Castie!

正态分布, 优劣伴生

北冥有鱼,其名为鲲(kūn)。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。


北海若曰:“井鼃不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。今尔出于崖涘,观于大海,乃知尔丑,尔将可与语大理矣。

iOS 移动端面向文档开发

之前的解耦架构生成器在实际项目中已经顺利测试通过了, 现在要做的是将文档规范出来, 并扩展到Android, HTML5端的共用, 实现面向文档开发.

参考链接:

1. 面向文档架构设计

文档要求:

  1. 视图操作需求, 包括页面中所有需要调用网络接口的逻辑, 比如跳转, 刷新等请求.
  2. 网络通信接口, 对于目前sender层的一次解耦封装, 目的是不过于依赖sender层, 避免一套请求逻辑写很多遍.
  3. 传输数据类型, 类似项目中的cachebean, 但不依赖cachebean. 针对不同需求可选添加状态机制, 页面遵循状态渲染.

文档协议样张:

#import <UIKit/UIKit.h>
#import <WonderfulMallHome.pb.h> 

@protocol HYSpecialSaleViewOperation <NSObject> //定义视图操作需求

- (void)pushToNextViewController:(NSString *)targetUrl;
- (void)pullDownRefresh;

@end

@protocol HYSpecialSaleModelInterface <NSObject> //定义传输数据类型

@property(nonatomic) NSInteger tabId ;
@property(nonatomic,strong) NSMutableArray * activityTemplates ;

@end

@protocol HYSpecialSaleViewModelInterface <NSObject> //定义网络通信接口

@property (nonatomic,strong) id<HYSpecialSaleModelInterface> model;

- (void)initializeWithParameter:(NSDictionary *)parameter finishedCallBack:(void(^)())finishCallBack;
- (void)pullDownRefreshWithFinishedCallBack:(void(^)())finishCallBack;

@end


@protocol HYSpecialSaleViewInterface <NSObject>

@property (nonatomic,strong) id<HYSpecialSaleViewModelInterface> hyspecialsaleViewModel;
@property (nonatomic,strong) id<HYSpecialSaleViewOperation> hyspecialsaleOperation;

@end

期望实现非开发人员通过阅读文档协议能够基础的了解该页面的大致逻辑功能, 并能够清晰的定位具体功能函数.

2. 架构设计模式浅析

├── Template //模板文件
│   ├── ControllerTemplate.h //控制器
│   ├── ControllerTemplate.m
│   ├── InterfaceTemplate.h //文档协议
│   ├── ModelTemplate.h //传输数据类型
│   ├── ModelTemplate.m
│   ├── PresenterTemplate.h //视图操作需求
│   ├── PresenterTemplate.m
│   ├── ViewModelTemplate.h //网络通信接口
│   ├── ViewModelTemplate.m
│   ├── ViewTemplate.h //UI层
│   └── ViewTemplate.m

根据文档协议, 生成各自的ModelTemplate, PresenterTemplate, ViewModelTemplate, ViewTemplate, 生成的对象必须遵循文档协议的定义, 控制器用来协调 PresenterTemplate, ViewModelTemplate, ViewTemplate 三者的通信

Model层

用来统筹页面使用的数据, 类似于项目中的cachebean, 但和cachebean不同的是, 可以在model中加入状态机制, UI逻辑可以根据网络请求后的状态机制来判断显示UI, 状态的制定根据具体产品文档的定义. 代码中可以使用枚举来区分状态.

#import <Foundation/Foundation.h>
#import "HYSpecialSaleInterface.h"

@interface HYSpecialSaleModel : NSObject <HYSpecialSaleModelInterface>

@property(nonatomic) NSInteger tabId ;
@property(nonatomic,strong) NSMutableArray * activityTemplates ;

- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
+ (HYSpecialSaleModel *)modelWithDictionary:(NSDictionary *)dictionary;

@end

Presenter层

用于统筹页面的交互逻辑, 这里的交互逻辑是需要调用接口的交互逻辑, 本职工作是用于将ViewModel的数据正确传输到View的过程, 并在此更改状态机, 用来实现UI层逻辑在View层内实现. 上一页面传输来的参数通过控制器传输至Presenter层进行保存. 每次用户交互后重置ViewModel的数据, 实现单向的动态绑定.

#import <UIKit/UIKit.h>
#import "HYSpecialSaleInterface.h"

@interface HYSpecialSalePresenter : NSObject<HYSpecialSaleViewOperation>

@property (nonatomic,assign) NSInteger tabId;
@property (nonatomic,weak) id<HYSpecialSaleViewInterface> hyspecialsaleView;
@property (nonatomic,weak) id<HYSpecialSaleViewModelInterface> hyspecialsaleViewModel;

- (void)adapterWithHYSpecialSaleView:(id<HYSpecialSaleViewInterface>)hyspecialsaleView hyspecialsaleViewModel:(id<HYSpecialSaleViewModelInterface>)hyspecialsaleViewModel;

@end

ViewModel层

用于统筹页面的接口调用, 对Model进行持有, 使得Model不直接与任意对象进行交互,确保数据的安全性, 可将所有的数据获取及修改数据结构的逻辑封装在该层, 可以实现接口的互相调用.

#import <Foundation/Foundation.h>
#import "HYSpecialSaleInterface.h"

@interface HYSpecialSaleViewModel : NSObject <HYSpecialSaleViewModelInterface>

@property (nonatomic,strong) id<HYSpecialSaleModelInterface> model;

- (void)initializeWithParameter:(NSDictionary *)parameter finishedCallBack:(void(^)())finishCallBack;

- (void)pullDownRefreshWithFinishedCallBack:(void(^)())finishCallBack;

@end

View层

用于UI层的展示, 所有的UI层都基于可复用的TableView, 持有用户交互操作及ViewModel数据的绑定. 在View层所有的用户交互实现直接调用文档协议中的视图操作函数即可, 不必关系具体的实现流程, 当请求完成后会更新ViewModel数据, 进行页面的刷新, 特殊的UI逻辑根据状态机进行判断即可.

#import <UIKit/UIKit.h>
#import "HYSpecialSaleInterface.h"

@interface HYSpecialSaleView : UIView <HYSpecialSaleViewInterface>

@property (nonatomic,strong) id<HYSpecialSaleViewOperation> hyspecialsaleOperation;
@property (nonatomic,strong) id<HYSpecialSaleViewModelInterface> hyspecialsaleViewModel;

@end

3. 文档设计代码规范

所有的函数方法的命名使用驼峰式的命名方法, 避免使用缩写, 如之前的cachebean缩写成cb这种是绝对要避免的, 对于BOOL值得命名需要在参数名前加上is来进行区分, 期望做到代码即为注释, 让非开发人员通过仅仅看代码就能够知道业务逻辑.

#import <UIKit/UIKit.h>

@protocol <#Unit#>ViewOperation <NSObject>
<#ViewOperation#>

@end

@protocol <#Unit#>ModelInterface <NSObject>
<#ModelInterface#>

@end

@protocol <#Unit#>ViewModelInterface <NSObject>

@property (nonatomic,strong) id<<#Unit#>ModelInterface> model;

- (void)initializeWithParameter:(NSDictionary *)parameter finishedCallBack:(void(^)())finishCallBack;
<#ViewModelInterface#>
@end

@protocol <#Unit#>ViewInterface <NSObject>

@property (nonatomic,strong) id<<#Unit#>ViewModelInterface> <#unit#>ViewModel;
@property (nonatomic,strong) id<<#Unit#>ViewOperation> <#unit#>Operation;

@end
{
    "super" : "", //父类的前缀
    "controller" : "Home", //控制器的前缀名
    "model" : { //模型的属性定义
        "models" : "Array"
    },
    "viewModel" : [ //获取数据的方法定义
        "register",
        "login",
        "exit",
        "refresh"
    ],
    "presenter" : [ //用户交互的方法定义
        "pushToNextViewController",
        "popToRootViewController",
        "showAlert",
        "closeButtonClick"
    ]
}

文档模板可以通过任意数据结构进行生成, 文档模板中的<#Unit#>为该页面的前缀名, 如”HYHome”, “HYSpecialSale”等.

Model的代码规范

根据cacheBean相同字段, 或使用cachebean的子类, 但由于代码逻辑清晰的缘故推荐使用生成的model层.

@property (nonatomic, assign) BOOL isPreLoad;

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (!_isPreLoad) {
        [self adapterView];
        _isPreLoad = YES;
    }
}

Bool类型的参数名推荐添加is前缀, 能够让非开发人员了解是Bool值.

typedef NS_OPTIONS(NSUInteger, UIControlState) {
    UIControlStateNormal       = 0,
    UIControlStateHighlighted  = 1 << 0,                  // used when UIControl isHighlighted is set
    UIControlStateDisabled     = 1 << 1,
    UIControlStateSelected     = 1 << 2,                  // flag usable by app (see below)
    UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
    UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
    UIControlStateReserved     = 0xFF000000               // flags reserved for internal framework use
};

状态机的定义可以参考系统的UIControl的形式创建, 可实现位移叠加状态.

Presenter的代码规范

根据具体需求制定方法名, 方法如果需要调用接口建议与接口名相同或类似, 尽可能的简化参数, 达到代码及注释的原则. 方法名使用驼峰形式, 不建议用缩写, 能够准确的描述方法用处为佳.

- (void)pushToNextViewController:(NSString *)targetUrl {
    [[HYCurrentVCmanager shareInstance].getCurrentVC hyPushDetail:targetUrl];
}

- (void)pullDownRefresh {
    
    __weak typeof(self) _self = self;
    __weak id<HYSpecialSaleViewModelInterface> __hyspecialsaleViewModel = _hyspecialsaleViewModel;
    [_hyspecialsaleViewModel pullDownRefreshWithFinishedCallBack:^{
        _self.hyspecialsaleView.hyspecialsaleViewModel = __hyspecialsaleViewModel;
    }];
}

UI相关逻辑操作使用状态机在View层进行操作, 不建议在Presenter书写, 会造成逻辑紊乱, 接口调用的时候仅需重新赋值ViewModel, 数据及状态机会动态进行更新.

ViewModel的代码规范

根据函数命名规范和之前类似, 请保证有传入参数及回调即可 – Parameter:(NSDictionary *)parameter finishedCallBack:(void (^)())finishCallBack.

- (void)dynamicBindingWithWithParameter:(NSDictionary *)parameter finishedCallBack:(void (^)())finishCallBack {

    [DataBase requestDataWithClass:[NSObject class] finishedCallBack:^(NSDictionary *response) {

        NSDictionary * data = response;
        NSArray * models = data[@"models"];

        [self.models removeAllObjects];
        for (NSDictionary * dict in models) {
            [self.models addObject:[ModelTemplate modelWithDictionary:dict]];
        }

        finishCallBack();
    }];

    [NetWork requestDataWithType:MethodGetType URLString:@"http://localhost:3001/api/J1/getJ1List" parameter:nil finishedCallBack:^(NSDictionary * response){

        NSDictionary * data = response[@"data"];
        NSArray * models = data[@"models"];

        [self.models removeAllObjects];
        for (NSDictionary * dict in models) {
            [self.models addObject:[ModelTemplate modelWithDictionary:dict]];
        }

        [DataBase cache:[NSObject class] data:data[@"models"]];
        finishCallBack();
    }];
}

可以在ViewModel层处理数据相逻辑的操作, 比如数据缓存, 和网络请求, 以及一些数据封装, 状态机建议写在Presenter层的回调中.

View的代码规范

View没有什么特别的代码规范, 根据不同需求灵活使用设计模式即可, 在ViewModel赋值set方法调用的函数中可以进行状态机的判断及刷新的操作.

- (void)setHyspecialsaleViewModel:(id<HYSpecialSaleViewModelInterface>)hyspecialsaleViewModel {
    _hyspecialsaleViewModel = hyspecialsaleViewModel;
    
    if (!_preLoaded) { //这种判断可以使用状态机来代替.
        [self sortSubViews:@[@{@"blockType" : @(SpecialBlockTypeBlockBanner), @"showTitle" : @(NO)},
                             @{@"blockType" : @(SpecialBlockTypeBlockOne),    @"showTitle" : @(NO)},
                             @{@"blockType" : @(SpecialBlockTypeBlockTwo),    @"showTitle" : @(NO)},
                             @{@"blockType" : @(SpecialBlockTypeBlockThree),  @"showTitle" : @(NO)},
                             @{@"blockType" : @(SpecialBlockTypeBlockFour),   @"showTitle" : @(NO)},
                             @{@"blockType" : @(SpecialBlockTypeBlockFive),   @"showTitle" : @(NO), @"showMain" : @(YES)}]];
        _tableView.userInteractionEnabled = NO;
        _preLoaded = YES;
    } else {
        NSMutableArray * arr = [NSMutableArray array];
        for (ActivityBlock * activityBlock in hyspecialsaleViewModel.model.activityTemplates) {
            BOOL showTitle = activityBlock.blockTitle.length ? YES : NO;
            BOOL showMain = activityBlock.banners.hyArray.count == 3 ? YES : NO;
            SpecialBlockType blockType = activityBlock.blockType;
            if (blockType == SpecialBlockTypeBlockFive) {
                [arr addObject:@{@"blockType" : @(blockType), @"showTitle" : @(showTitle), @"showMain" : @(showMain)}];
            } else {
                [arr addObject:@{@"blockType" : @(blockType), @"showTitle" : @(showTitle)}];
            }
        }
        _tableView.userInteractionEnabled = YES;
        [self sortSubViews:arr];
    }
        [_tableView reloadData]; //刷新列表
}

在View中可以直接使用之前的operation的所有用户交互的函数.

小建议

文档协议的建议:

  1. 第一次生成后如需添加新的方法, 接口或数据结构等, 都首先修改文档协议, 并遵守文档协议的规范
  2. 文档协议尽量保持清晰和完整, 有必要的情况可以添加注释让文档更加清晰
  3. 不要随意修改之前的文档协议, 如需修改请事先进行沟通

命名规范的建议:

  1. 不要使用缩写, 尽量使用自然语言让非开发人员能够了解功能, 所谓代码即注释
  2. 建议使用 is, set, get, did, will, ed之类表示逻辑状态的关键字, 能够清晰的读懂方法名
  3. 如果英语语法不清楚的同学可以使用Google翻译.

UI层的建议:

  1. cell使用Adapter适配器模式用以适配不同的数据结构
  2. cell的高度和内容封装在一个文件中, 进行解耦.
  3. operation可以贯穿子控件, 不建议使用delegate和block.

具体说明可以下载Demo来进行共同探讨.

最近的文章

iOS 通俗易懂的HTTP网络

去了饿厂面试后了解到了自己计算机基础的薄弱, 非科班出身薄弱也是自然的, 说实话, 我也并不是特别想要往底层深究, 因为越底层的东西越会抽象成服务输送给大众, 就好比自来水, 一般人都不会想要去了解自来水的底层逻辑吧, 但作为开发者, 我们还是得了解下基础的网络概念.序关于HTTP与HTML的发明有个很有趣的插曲, 那就是首个万维网服务器与浏览器是在一台NeXTStep计算机上编写的, 在1997年, Apple收购了NeXTStep Computer并将NeXTStep作为mac O...…

移动开发继续阅读
更早的文章

Web Vue项目速转.htm静态网页

拖了2个星期, 终于可以结束简历制作的项目了, 说实话这个项目其实挺没有技术含量的, 话说我也没什么技术, 对于前端方面的技术也仅仅是浅显的认知, 要更加深入的进行学习的话算法也就是数学知识真的是一个很大的瓶颈, 所以这一话后准备潜心修炼自己薄弱的数学知识.参考链接: Web 是时候用前端写个简历了! Web 前端项目要从基本布局开始 Web 简历一定要设计的美美的 Web 使用Vue代替陈旧的jQuery以下内容在上述文章基础上进行, 请事先查阅.设计模式是小白晋升码农的利器...…

前端开发继续阅读