一、前言
关于网络层,苹果对网络请求部分已经做了很好的封装,业界的AFNetworking也被广泛使用,除此以外,肯定还有其他的网络框架,但在实际的App开发中,AFNetworking已经成为了事实上各大App的标准配置。
我们一直都有讲分层架构,其中很重要的一层就是网络层,那我们到底改如何设计才能更好的辅助我们的项目呢?最近也看了一些大牛的文章,也是有所获。
二、问题简要
1.以什么方式将数据交付给业务层?
2.交付什么样的数据给业务层?
3.集约型API调用方式和离散型API调用方式
三、具体聊聊上面三个问题
1.以什么方式将数据交付给业务层?
iOS开发领域有很多对象间数据的传递方式,我看到的大多数App在网络层所采用的方案主要集中于这三种:Delegate,Notification,Block。
我当前开发的项目就是采用Block。但我今天要讲是以Delegate为主,Notification为辅。原因如下:
1.尽可能减少跨层数据交流的可能,限制耦合
2.统一回调方法,便于调试和维护
3.在跟业务层对接的部分只采用一种对接手段限制灵活性,以此来交换应用的可维护性
尽可能减少跨层数据交流的可能,限制耦合
什么叫跨层数据交流?就是某一层(或模块)跟另外的与之没有直接对接关系的层(或模块)产生了数据交换。为什么这种情况不好?严格来说应该是大部分情况都不好,有的时候跨层数据交流确实也是一种需求。之所以说不好的地方在于,它会导致代码混乱,破坏模块的封装性。我们在做分层架构的目的其中之一就在于下层对上层有一次抽象,让上层可以不必关心下层细节而执行自己的业务。所以我们尽量不选择Notification。
然后为什么尽量不要用block?
block和delegate乍看上去在作用上是很相似,但是关于它们的选型有一条严格的规范:当回调之后要做的任务在每次回调时都是一致的情况下,选择delegate,在回调之后要做的任务在每次回调时无法保证一致,选择block
。
在离散型调用的场景下,每一次回调都是能够保证任务一致的,因此适用delegate。这也是苹果原生的网络调用也采用delegate的原因,因为苹果也是基于离散模型去设计网络调用的。
在集约型调用的场景下,使用block是合理的,因为每次请求的类型都不一样,那么自然回调要做的任务也都会不一样,因此只能采用block。AFNetworking就是属于集约型调用,因此它采用了block来做回调。
统一回调方法,便于调试和维护
首先,Block本身无好坏对错之分,只有合适不合适。在这一节要讲的情况里,Block无法做到回调方法的统一,调试和维护的时候也很难在调用栈上显示出来。
在网络请求和网络层接受请求的地方时,使用Block没问题。但是在获得数据交给业务方时,最好还是通过Delegate去通知到业务方。因为Block所包含的回调代码跟调用逻辑放在同一个地方,会导致那部分代码变得很长,因为这里面包括了调用前和调用后的逻辑。从另一个角度说,这在一定程度上违背了single function,single task
的原则,在需要调用API的地方,就只要写API调用相关的代码,在回调的地方,写回调的代码。
然而大部分App里,当我们写代码写到这边的时候,也意识到了这个问题。因此我们会在block里面写个一句话的方法接收参数,然后做转发,然后就可以把这个方法放在其他地方了,绕过了Block的回调着陆点不统一的情况。比如这样:
[API callApiWithParam:param successed:^(Response *response){
[self successedWithResponse:response];
} failed:^(Request *request, NSError *error){
[self failedWithRequest:request error:error];
}];
这实质上跟使用Delegate的手段没有什么区别,只是绕了一下,不过还是没有解决统一回调方法的问题。Block是目前大部分第三方网络库都采用的方式,因为在发送请求的那一部分,使用Block能够比较简洁,因此在请求那一层是没有问题的,只是在交换数据之后,还是转变成delegate比较好,比如AFNetworking里面:
[AFNetworkingAPI callApiWithParam:self.param successed:^(Response *response){
if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
[self.delegate successedWithResponse:response];
}
} failed:^(Request *request, NSError *error){
if ([self.delegate respondsToSelector:@selector(failedWithResponse:)]) {
[self failedWithRequest:request error:error];
}
}];
这样在业务方这边回调函数就能够比较统一,便于维护。
2.交付什么样的数据给业务层?
其实我们的理想情况是希望API的数据下发之后就能够直接被View所展示。但是,这怎么可能呢?UI可能一天一变,你能让API也天天变?,另外,这种做法使得View和API联系紧密,也是我们不希望发生的。
这里我们引入了reformer(名字而已,叫什么都好)这个对象用于封装数据转化的逻辑,这个对象是一个独立对象,事实上,它是作为Adaptor模式存在的。我们可以这么理解:想象一下我们洗澡时候使用的莲蓬头,水管里出来的水是API下发的原始数据。reformer就是莲蓬头上的不同水流挡板,需要什么模式,就拨到什么模式。(其实它就是个专业处理数据的)
先定义一个protocol:
@protocol ReformerProtocol
- (ViewModel *)reformDataWithManager:(APIManager *)manager;
@end
在Controller里是这样:
@property (nonatomic, strong) id XXXReformer;
@property (nonatomic, strong) id YYYReformer;
#pragma mark - APIManagerDelegate
- (void)apiManagerDidSuccess:(APIManager *)manager {
XXXViewModel *reformedXXXData = [manager fetchDataWithReformer:self.XXXReformer];
[self.XXXView configWithData:reformedXXXData];
YYYViewModel *reformedYYYData = [manager fetchDataWithReformer:self.YYYReformer];
[self.YYYView configWithData:reformedYYYData];
}
在APIManager里面,fetchDataWithReformer是这样:
- (ViewModel *)fetchDataWithReformer:(id)reformer {
if (reformer == nil) {
return self.rawData;
} else {
return [reformer reformDataWithManager:self];
}
}
为了保持数据可读性,我们这里再引入一个ViewModel,其实就是相关View的模型。一个控件对应一个属性。
PropertyListReformer.m
- (PropertListCellModel *)reformData:(NSDictionary *)originData fromManager:(APIManager *)manager {
... ...
... ...
PropertListCellModel *resultData = nil;
if ([manager isKindOfClass:[FirstListAPIManager class]]) {
resultData = ...
}
if ([manager isKindOfClass:[SecondListAPIManager class]]) {
resultData = ...
}
if ([manager isKindOfClass:[ThirdListAPIManager class]]) {
resultData = ...
}
return resultData;
}
PropertListCell.m
- (void)configWithData:(PropertListCellModel *)model {
self.imageView.image = model.image;
self.idLabel.text = model.id
self.nameLabel.text = model.name;
self.titleLabel.text = model.title;
}
3.集约型API调用方式和离散型API调用方式
集约型API调用其实就是所有API的调用只有一个类,然后这个类接收API名字,API参数,以及回调着陆点(可以是target-action,或者block,或者delegate等各种模式的着陆点)作为参数。然后执行类似startRequest这样的方法,它就会去根据这些参数起飞去调用API了,然后获得API数据之后再根据指定的着陆点去着陆。
集约型API调用方式:
[APIRequest startRequestWithParams:params success:^(Response *response){
...
} fail:^(Error *error){
...
}];
离散型API调用是这样的,一个API对应于一个APIManager,然后这个APIManager只需要提供参数就能起飞,API名字、着陆方式都已经集成入APIManager中。
离散型API调用方式:
@property (nonatomic, strong) ItemListAPIManager *itemListAPIManager;
// getter
- (ItemListAPIManager *)itemListAPIManager {
if (!_itemListAPIManager) {
_itemListAPIManager = [[ItemListAPIManager alloc] init];
_itemListAPIManager.delegate = self;
}
return _itemListAPIManager;
}
// 使用的时候就这么写:
[self.itemListAPIManager loadDataWithParams:params];
- (void)successWithResponse:(Response *)response { ... }
- (void) failedWithResponse:(Error *) error { ... }
四、总结
1.使用delegate来做数据对接,仅在必要时采用Notification来做跨层访问
2.交付NSDictionary给业务层,使用Const字符串作为Key来保持可读性
3.提供reformer机制来处理网络层反馈的数据,这个机制很重要,好处极多
4.网络层上部分使用离散型设计,下部分使用集约型设计
五、写在最后
注:本文主体思想是学习了Casa Taloyum 的一篇关于网络框架的文章,很有收获,于是有了一些学习感悟,也感谢大牛的文章。并附上原文如下:
iOS应用架构谈 网络层设计方案