作者 kyson老师 2017.01.11 17:51:00 高解耦的网络请求框架Mangogo 评论:3 说起解耦,其实说白了,就是大家各自干各自的事情,相互不干扰。软件开发中就是【展示层】---【逻辑业务层】----【数据层】不相互干扰。只要接触过iOS开发的人都知道AFNetworking这个网络请求库,它封装了iOS自身的NSURLSession或者NSURLConnection,提供了各种各样的网络请求功能。正式的开发中是业务层需要请求服务器获取数据并刷新UI。在请求的时候只要指明请求的URL和参数即可,然后在规定的地方拿到回调。可能大家觉得其实这已经很“解耦”了,有必要自己再写套框架来多此一举嘛,别急着发牢骚,请先回答我的几个问题: 1.多个页面调用同一个网络请求,我是否每个页面都要写一遍;某个网络请求改了数据返回类型,我是否需要每个涉及到这个请求的页面都要重新改一遍? 2.开发前期服务端没有走通数据的情况下如何模拟网络请求? 3.一般每个公司都会订一套网络返回数据格式,是否每个页面都需要做一次判断处理(主要是errorcode,errormessage等固定的字段) 4.如何有效的进行单元测试? 5.更改了新的网络三方库怎么办? 如果对于以上的问题你完全没有考虑过或者考虑过但无从下手,那么我只能说兄弟,是时候好好看看我的文章了哈哈哈哈。 其实我的这个框架说简单其实也很简单,我们先纵览一下它的大概流程 ------------------------------------------------------ |---------------| | | controller | |-------------| |_______________| | Mangogo | | -------------- | | |do request--------------------->| | | | | | <--------------------call back | | | Mogo内部的流程图如下(请忽略每个请求前面的AGJ这三个字母,是我上家公司的缩写,现在为了适应统一化,前缀已改成了MG): ![uml_mongo.jpg][1] 其中: 1.NetworkAccess 负责具体网络请求的实施 2.NetworkServiceMediator 负责网络请求的分发 3.TaskPool负责管理网络请求线程池 具体到代码中,我们需要在Controller中调用doService方法,这个方法将网络请求作为一个服务来处理,每个网络请求对应一个服务,使用宏定义的URL相对路径用于区分每个请求。调用请求后,首先由TaskPool分配线程并通过ServiceMediator分发到具体的网络请求,分发请求也是通过以URL作为Token定位到具体的请求的。 大概的流程知道后,我们就可以开始一步步分析网络请求的整个流程了 TaskPool 管理网络请求线程池 -------------------- 每次调用doService就会创建一个线程,但不会无限创建,上限是5个(当然可以根据你的需要设置成其他数量),做成串行队列用于依次接受网络请求。 taskpool还负责获取网络请求的回调并回传给相应对象,这一步是通过多播代理类TaskPoolDelegateManager实现的。做过XMPP开发的都知道多播代理的好处,它能管理代理本身(其实通知的实现方式也是类似于多播代理的)。TaskPoolDelegateManager管理的TaskPoolDelegate的属性delegate是这么申明的: @property (nonatomic, weak) id delegate; 通过weak关键字可以避免忘记释放对象引起的内存泄露。当然,不用多播代理也是可以的,这里使用多播代理的另外一个原因是要管理每个请求的回调,确保A发出的a请求,b请求,c请求回调后能对应到对应的a回调,b回调,c回调。实现起来也并不难,代码如下: //添加到token NSMutableArray *requestTokens = [[NSMutableArray alloc] initWithArray:self.requestTokens]; //获取对象的唯一辨识,目前没什么好方法,暂时用hash NSString *resultServiceName = [NSString stringWithFormat:@"%lu.%@.%@",(unsigned long)self,serviceName,params]; [requestTokens addObject:resultServiceName]; 以及 ``` for (NSString *requestToken in self.requestTokens) { NSString *resultServiceName = [NSString stringWithFormat:@"%lu.%@.%@",(unsigned long)self,service.serviceName,((MGNetwokResponse *)response).requestParams]; if ([requestToken isEqualToString:resultServiceName]) { [self serviceName:service.serviceName response:response]; NSMutableArray *tempTokens = [NSMutableArray arrayWithArray:self.requestTokens]; [tempTokens removeObject:resultServiceName]; self.requestTokens = tempTokens; } } ``` 这两段代码,第一段是加token,用于区分每个网络请求,第二段是去除请求的对象对应的token。那么,这个token就是用于区分同一个对象的不同请求 ServiceMediator 分发网络请求 ---------------------------- Mediator,中介者,因为是Operation的子类,因此每个Mediator被加到TaskPool后会自动执行main函数,main函数中调用一些预先定义的接口供下层调用。需要注意的是,TaskPool不会具体的去调用具体的ServiceMediator,这样就失去了框架的扩展性。这里的处理方式是,TaskPool会留一个注册ServiceMediator的方法,这个方法就是指定ServiceMediator的某个子类来做具体的NetworkAccess请求。 在ServiceMediator中有如下代码: ``` NSMethodSignature *methodSignature = [[self class] instanceMethodSignatureForSelector:service]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; [invocation setTarget:self]; [invocation setSelector:service]; [invocation retainArguments]; ``` 这么做是为了调用相应的方法,弥补`performSelector: withObject: `等函数的不足 NetworkAccess 具体网络请求实现 ----------------------------------------- 一个典型的网络请求是这样的: (1)通过方法 ```-(instancetype)initWithHost:(NSString *) host modulePath:(NSString *) path;``` 初始化,其中,host是地址,path是资源路径 (2)通过```-(MGNetwokResponse *)doHttpRequest:(NSString *)requestUrlString params:(NSDictionary *) params;``` 做具体的网络请求。 iOS默认的网络请求是异步的,但NetworkAccess是需要阻塞运行的,因为NetworkAccess本身已经被丢进线程中执行了。因此NetworkAccess内部是通过[信号量][2]将异步请求转换成同步请求。 信号量各位在iOS开发中可能使用的不多,但确实是多线程开发中特别好用的技巧之一: 第一步:通过```dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);``` 创建一个信号量 当网络请求发出, 第二步:需要等后台返回数据时调用`dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);`方法等待接受收据。等数据接受完毕后, 第三步:调用`dispatch_semaphore_signal(semaphore);`代码将恢复到第二步后执行。 以上就是Mangogo的请求流程了,有问题的还请赐教 github地址:[https://github.com/kysonzhu/Mangogo.git][3] ---------- 最后解答一下文章开头的几个问题^_^ 1.模拟网络请求 也就是Mock请求,对应的类是MGMockAccess,我们只需要在NetworkServiceMediator中将请求的HOST地址改为HOST_MOCK即可,具体的看里面的Demo吧 2.单元测试 新建一个ServiceMediator并调用start命令即可,因为ServiceMediator是一个线程 3.缺省的json数据处理 对应的类是MGJsonHandler,处理后丢给对象MGNetwokResponse。其中MGNetwokResponse中的rawJson是原始数据,errorCode是错误码,errorMessage是错误消息,responseObj是过滤后的对象,可以是数组或字典。 [1]: http://kyson.cn/usr/uploads/2017/01/1279137630.jpg [2]: http://baike.baidu.com/view/1303265.htm [3]: https://github.com/kysonzhu/Mangogo.git 赏 标签:ios网络框架单元测试信号量多线程多播代理
噗,,空
很有封装思想。
哈哈哈哈哈