之前的解耦架构生成器在实际项目中已经顺利测试通过了, 现在要做的是将文档规范出来, 并扩展到Android, HTML5端的共用, 实现面向文档开发.
参考链接:
1. 面向文档架构设计
文档要求:
- 视图操作需求, 包括页面中所有需要调用网络接口的逻辑, 比如跳转, 刷新等请求.
- 网络通信接口, 对于目前sender层的一次解耦封装, 目的是不过于依赖sender层, 避免一套请求逻辑写很多遍.
- 传输数据类型, 类似项目中的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
的所有用户交互的函数.
小建议
文档协议的建议:
- 第一次生成后如需添加新的方法, 接口或数据结构等, 都首先修改文档协议, 并遵守文档协议的规范
- 文档协议尽量保持清晰和完整, 有必要的情况可以添加注释让文档更加清晰
- 不要随意修改之前的文档协议, 如需修改请事先进行沟通
命名规范的建议:
- 不要使用缩写, 尽量使用自然语言让非开发人员能够了解功能, 所谓代码即注释
- 建议使用
is
,set
,get
,did
,will
,ed
之类表示逻辑状态的关键字, 能够清晰的读懂方法名 - 如果英语语法不清楚的同学可以使用Google翻译.
UI层的建议:
- cell使用Adapter适配器模式用以适配不同的数据结构
- cell的高度和内容封装在一个文件中, 进行解耦.
- operation可以贯穿子控件, 不建议使用delegate和block.
具体说明可以下载Demo来进行共同探讨.