关于网络层的思考
一般而言,和网络层的数据传输无外乎三种:代理,block。在网上如果看过相关的架构设计方面的资料,大多会将这两种架构归为离散型和集约型架构,这两种泾渭分明,有利有弊,这里 这里有一个较好的架构资料,比较好的分析了优缺点。
xx股票采用的网络层使用的是代理模式,这里各个Controller与网络层使用了代理模式来获取网络数据,其实就是集约型的网络框架。上一篇文章说道,由于将之前两层换做三层,这样的话新加入的中间层充当了网络的前置层(它其实就是业务的逻辑层,也是具体发送网络请求,请求网络数据的回调层),这样,AccountManager就和上层之间形成了代理模式,具体如图:
.
网络层代理模式哪里不好
代理模式在这里提供了一个比较好的模式供给网络层的数据返回,因为在数据收发当中,给下层一个比较自由的空间来主推数据给上层,哪怕上层请求并未发出。但是,代理模式下的网络结构,我觉得不好的有如下几点:
- 网络回调复杂,上下文关联麻烦 代理模式对业务其实是有打断操作的。比如这段代码:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[Module defaultModule] requestXX];//要做某事
}
- (void)requestXXDidReceive:(SomeModel *)dataModel{
//接着做某事
}
可以看到,这里如果使用block的形式,数据其实是可以前后关联的,但是如果是代理模式,接着做某事确实是比较麻烦的事情,因为你的上下文数据在A函数,而具体做事的在B函数,数据还必须要保留。下面的aData就是一个可有可无的全局临时变量,只是为了做上下文保存而存在的。
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSString *aData = @"";
self.aData = aData;
[[Module defaultModule] requestXX];//要做某事
}
- (void)requestXXDidReceive:(SomeModel *)dataModel{
//接着做某事,need aData
aData = self.aData;
}
- 需要较强的监听控制,回调地址也是太多了
在AccountManager的中心结构的代理模式中,如何确保正确回调给真正的发送发?AController和BController都监听回调,但AController发送了请求,如果确保B不受到错误的回调?这里就需要很精细的监听和切断监听。XX股票的代码里,按照协议的成功和失败,作为两个回调进行了回调:Success和Failed,所以若一个对象有X个事件,那么就存在2*x个回调地址,在某些复杂的页面上,可以看到大量的数据收发和回调。而回调的大量堆砌,使得业务很复杂。
.
- 头疼的串行请求 那么,如果网络请求串行,你猜猜应该怎么逐条发送?
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSString *aData = @"";
self.aData = aData;
[[Module defaultModule] requestXX];//要做某事
}
- (void)requestXXDidReceive:(SomeModel *)dataModel{
//接着做某事,need aData
aData = self.aData;
[[Module defaultModule] requestBB];//要做某事
}
那么,假如现在我只需要发送requestXX,而不发送requestBB呢?
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSString *aData = @"";
self.aData = aData;
[[Module defaultModule] requestBB];//要做某事
}
- (void)requestXXDidReceive:(SomeModel *)dataModel{
//接着做某事,need aData
aData = self.aData;
if(justSendXX){
[[Module defaultModule] requestXX]
retuen;
}
[[Module defaultModule] requestBB];//要做某事
}
如果这个时候要求requestXX是所有协议的基础协议呢,requestBB,requestCC,requestDD都基于requestXX发送回调,而且不同返回值调用不同的下一条请求呢?真是整个人都不太好了。
当我要重构AccountManager的时候,在重构登录注册第三方登录鉴权等地方,发现了很多Controller里面要串行三四条协议才能完成一个基本业务。即A协议数据需要B,B协议数据需要C,那么从C开始发送,C->B->A,那么按照现在这种传递方法,假如还存在C->E->B的方式,那如何是好?在C函数里面,要判断当前需要继续请求E还是B,同样,在B这个函数,要判断上一个调用时来自于E还是C,这样又多了一层判断,所以就存在大量的判断标志位。
.
由于大量的串行协议都在这里指定,导致了该层代码复杂,当串行B->A 与 B->X都要发送时,B被多条协议依赖,导致B协议成功后,要去判断当前要跳转的下一个是哪个(A或X)。而由于每一条的协议被复用,导致当一条协议是很多业务的起手协议时,这个协议的成功回调下面就需要大量的判断逻辑。
.
尤其在需要重构的第三方登录模块,有大量这种串行代码,一个Controller里面要十几个标志位标记,要记录一条协议发送到哪里了,下一条协议应该发送给谁,哪些数据是需要的,哪些数据仅仅用于串行协议,而如果还要考虑串行过程中某些协议失败了,需要删除垃圾数据…那真的是不能太棒了。
对串行网络优化
是不是一开始就错了。如果不使用代理,而是使用闭包呢,由于闭包可以将代码块封装,假如做一个基于消息队列的组件,将这些消息的收发接管过来,以一种统一的形式向上传递,比如,我的期望串行数据传递模式:
[[[help addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestAA];
}] addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestBB];
}] addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestCC];
}]] ;
这里通过闭包将指定的串行模式定义下来。就完全不用记录每一条协议下一步的传递方向了。如果是A->E->C,则是:
[[[help addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestAA];
}] addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestEE];
}] addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestCC];
}]] ;
在最开始就定义好传递方向,将这样的代码封装进AccountManager里面,形成一个业务模块,那真就太好了。另外,我需要如下扩充:
- 所有串行协议都逐个成功,则统一回调一个业务成功
- 在串行协议中,只要出现一条协议失败,就统一回调一个业务失败
按照如下扩充,关注的点不再是协议,而是业务的成功失败。那么,就应该像这样:
ChainRequest *chainRequest = [ChainRequest CreateChainRequestWhihSuccCallback:^(AccountManager *module) {
succblock(self);
} FailedCallback:^(AccountManager *module) {
failblock(self);
}];
[[chainRequest addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestXX];
}] addRequest:^NSString *(AccountManager *module) {
return [[AccountManager sharedProfile] requestDD];
}] ;
[chainRequest run];
这里chainRequest作为一个业务载体,具体协议作为载体事件,决定载体的成功失败的导向。那么现在关心数据是怎么流动的,假如是CC->BB->AA的方式发送数据,那么必然是CC返回了BB的生产数据。前面说到,由于多加入了一层业务逻辑层,那新加入的层和UI层形成中心模式的代理结构,那么这样的话,网络层返回的数据只存在一份拷贝,新的数据流将冲刷之前的协议数据。那么,数据传递将通过AccountManager作为中转,逐个在每个协议的发送前提供上一轮的数据(事实上,AccountManager可以连接同一级别的model,这样就能让每个回来的协议都不被上个协议所干扰)。而返回的NSString是为了解决当前集约型的网络框架串行发送的问题,由于之前是UI层代理模式传输,如果转而通过chainRequest来管理数据的收发,那么必然chainRequest要成为所有协议的被代理方,所有的回调都要跑到chainRequest里面去。那么,如果都跑到里面去,我如何动态的知道这条协议的回调方法呢?因为我不可能预先写好所有协议的回调地址,因为不是在chainRequest里发送的协议,必然没必要去监听返回的回调。
既然需要动态的当然,只能是反射机制了。每个协议一旦发送,我们都可以将这条协议的名称做保留,推算出回调的方法,再将方法使用
SEL selSucc = NSSelectorFromString(callBackSuccName);
class_addMethod([ChainRequest class], selSucc, class_getMethodImplementation([ChainRequest class], @selector(nowSuccBackback:)), "v@:@");
动态的添加方法,使得chainRequest具有监听回调的能力,每个回调再串到下一个协议发送的地方,这样,一个串行框架就出来了。新引入的ChainRequest其实是一个工具类,完全没有影响现有的框架结构,形成了一种插件式的方式:引入ChainRequest,接管AccountManager的回调,当所有的协议事件都成功即抛出成功事件给调用方,取消监听,自我销毁。当串行协议出现失败的时候,那么抛出失败事件给调用方,取消监听,也自我销毁。
另外如何串行输出,其实只需要对遍历一遍加入block的容器就好了:
#pragma mark - 统一回调方法
- (void)nowSuccBackback:(AccountManager *)module{
if (self.apiIndex < self.apiArray.count) {
log(@"将要执行下一个协议");
[self run:module];
}else{
log(@"协议执行完毕");
self.succBk(module);
[self clearMySelf];
}
}
如何打断串行,或者路由其他串行
假设一种场景,当A->B->C,A->B->D两种串行协议中,B要发给C还是D还是停止发送,其实提前是不知道的,而是要等A回来的结果来确定。这里在这个框架下是很好做的,这里由于每次执行的block中,已经拿到了上一次的结果,返回的NSString表示本次发送的协议名,如果返回nil,则可以打断协议(打断协议则默认表示业务失败)。
最后,它应该是这样的:
.
反思,这样的架构好在哪里?
- 由于串行协议是在整个业务场景中不到20%,也只有在较为集中的几个场景中存在密集的串行协议,不应该为了串行协议更改现有框架。
- 使用工具类,将之前自己收发改为只制定串行规则,然后让工具类收发。将调用方从具体的业务中解脱出来。
- 使用的工具类,只在AccountManager所在层使用,即数据来自于AccountManager,成功失败也仅告诉AccountManager,不存在跨层的行为。
- 解决了使用代理模式下,回调散落在各处,需要在各个散落点将业务串起来的糟糕写法,改为使用block,在工具类中逐个执行block,代理回调的点被工具类指定到同一方法,确保了着落点唯一性。