很久没写博客了。个人博客地址。以前一直在研究MVVM这种新的开发模式。也算是沉淀了一段时间,国庆期间可能还会写写MVVM。今天要聊的是创萌工做室的iOS客户端网络请求的封装。由于不少缘由封装的还不够好存在不少问题今天写出来只是想把最近作的事情写出来做个记录。html
我写这篇文章已经作好了被喷死吐槽死的准备了,由于我感受我封装的太有问题了,可是又真的很想写一写,毕竟对我来讲确实解决了一个大问题。ios
加入工做室一年。一共作了有三四个项目了。我先大概叙述一下咱们的网络请求的转变过程git
第一阶段 直接调用AFNetworking
第一个项目那时候懵懂无知,每一个界面都直接调AFNetworking。这样致使的问题就是代码量骤然增长。github
第二阶段 直接调用YTKNetwork
第二个项目刚作的时候唐巧开源了他们在猿题库封装的YTKNetwork。那时候仍是懵懂无知,仍是在每一个界面都开始直接调YTKNetwork。人家封装的那么好的东西就被我用成这个栽子,简直对不起巧大。我记得有个界面好像有5条网络请求,能够想象代码的冗余度。json
第三阶段 开始使用ReactiveCocoa
由于在作第二个项目的时候我和迪哥负责不一样的客户端。迪哥在看了limboy的基于AFNetworking2.0和ReactiveCocoa2.1的iOS REST Client开始将网络请求剥离到一个专门的界面,这样每次就不用写不少的东西了。数组
limboy用了AFNetworking-RACExtensions来实现回调的效果。简单的说就是subscribe一个信号,而后信号会返回一个信号回来,这样就实现了将网络请求部分剥离的效果。缓存
我在第二个项目基本上结束的时候上线了一个本身独立开发的app,里面就是用了这样的方法。服务器
这学期在作项目的时候仍是在沿用迪哥的代码,可是我发现了不少问题。网络
一是limboy在写rac代码的时候用的concat没法完成网络请求再请求。简单的说,服务器在返回告诉我session失效的时候我先须要后台自动登陆而后再次网络请求。我再写的时候concat没法实现再次网络请求,我也不太明白为何,试了各类方法都不行。这是很困惑个人我后面还须要再次研究一下。session
二是再判断服务器返回的东西的时候,须要判断状态码。若是成功,那么会有json数据返回回来,若是失败则没有数据。出现这样的状况若是我在Acontroller调Bcontroller的网络请求则还须要判断是否是返回了一个数组或者对象,若是是,开始对数据进行处理,若是不是,还得从新进行网络请求,由于说明session失效了。
这样仍是致使了网络请求部分有大量的代码。
Coding的iOS客户端是开源的,在Github和Coding官网都有。我放的连接是一个下载下来就能跑起来的。(强迫症,跑不起来的代码都不想看..不过如今看MVVM好多都跑不起来也硬着头皮看了)
Coding的网络请求本身看了。Coding是用block来进行回调的。至于这一块选择notice仍是block仍是delegate,能够参考iOS应用架构谈 网络层设计方案我算是认真看了,可是不是很能写的出来...
插一句话,咱们为何不用block。由于迪哥也不太会block就直接上rac了,我以前的博客写过简单的block,我在写代码的时候用delegate和notice比较多因此对block的实践比较少。这是我自身的问题。并且说实话我以为用rac挺好的,由于block加上typedef啥的其实不少东西的,不像rac直接调就完事了。
下面来解释一下,首先第一个block是咱们的主viewcontroller,也就是咱们逻辑部分和视图部分。首先第一个block调Coding_NetAPIManager里的函数。而后在Coding_NetAPIManager再调CodingNetAPIClient里的函数。
咱们倒着来讲。
第三部分 AFNetworking
在我分类的AFNetworking里也就是CodingNetAPIClient里,Coding进行了一件事情,那就是进行AFNetworking的网络请求。
在获取到数据的时候的对reponse进行一个判断。在判断数据的时候,若是数据有错误,则直接显示错误的msg,若是没有错误,那么则不返回任何东西。
而后在网络请求中判断,若是有错误,那么返回nil和id类型的error。若是没错误,返回response和nil。
第二部分 block
在这部分里,回调的结果有两种,一种是有数据,一种是没数据。其实到这就好了。那么如今在这第二个部分干什么呢,json转model。就这么简单。返回的东西,若是有数据返回,那么就再次返回model或者是data和nil,若是没有数据返回,就返回nil和error。
第一部分 block
到这里,其实只要判断有无数据就能够啦。
好了。下面咱们只须要用RAC来替换block就完成了。固然了,中间有坑,不会那么简单的...
我要上代码了。依然三部分。咱们仍是倒着来。我放关键的代码在这。
既然我把Coding的代码分红了
那么个人基本上就能够说是
也是倒着来。
第三部分 AFNetworking
这是AFNetworking网络请求
//一切仿照Coding case Get:{ return [[[[self rac_GET:aPath parameters:params] map:^id(RACTuple *JSONAndHeaders) { NSDictionary *responseObject = JSONAndHeaders[0]; DebugLog(@"\n===========response===========\n%@:\n%@", aPath, responseObject); //这里调用下一个部分的函数 id error = [self handleResponse:responseObject autoShowError:autoShowError rerequestJsonDataWithPath:aPath withParams:params withMethodType:method]; if (error) { return RACTuplePack(nil, error); }else{ return RACTuplePack(responseObject, nil); } }] catch:^RACSignal *(NSError *error) { DebugLog(@"\n===========response===========\n%@:\n%@", aPath, error); return [self showError:error]; }] replayLazily]; break; }
-(id)handleResponse:(id)responseJSON autoShowError:(BOOL)autoShowError rerequestJsonDataWithPath:(NSString *)aPath withParams:(NSDictionary*)params withMethodType:(NetworkMethod)method{ NSError *error = nil; NSNumber *resultCode = [responseJSON valueForKeyPath:@"status"]; //若是服务器返回的值不是正确有数值的话 if (resultCode.intValue != VALUE) { error = [NSError errorWithDomain:BASE_URL code:resultCode.intValue userInfo:responseJSON]; //若是服务器返回session失效的错误码 if (resultCode.intValue == VALUE) {//用户未登陆 [[[NetWork sharedManager] login] subscribeNext:^(RACTuple *x) { RACTupleUnpack(id data) = x; //因为没登录那么这里调用第二个部分RAC的登录方法,进行从新登录 if (data) { //这时有数据返回则再次发出网络请求 [self rerequestJsonDataWithPath:aPath withParams:params withMethodType:method]; } else { } }]; }else{ if (autoShowError) { [self showError:error]; } } } return error; }
//这是从新登录后再次进行网络请求 - (void)rerequestJsonDataWithPath:(NSString *)aPath withParams:(NSDictionary*)params withMethodType:(NetworkMethod)method { [[[NetWorkCheck sharedJsonClient] requestJsonDataWithPath:aPath withParams:params withMethodType:Get] subscribeNext:^(id x) { NSLog(@"success"); }]; }
第二部分 RAC
- (RACSignal *)test2 { NSString *path = @"/MyList.do"; NSDictionary *params = @{@"id":@"22"}; return [[[NetWorkCheck sharedJsonClient] requestJsonDataWithPath:path withParams:params withMethodType:Get] map:^id(RACTuple *x) { RACTupleUnpack(id resultData, NSError *error) = x; if (resultData) { return RACTuplePack(resultData, nil); } else { return RACTuplePack(nil, error); } }]; }
第一部分 RAC
- (IBAction)test2:(id)sender { [[[NetWork sharedManager] test2] subscribeNext:^(RACTuple *x) { RACTupleUnpack(id data) = x; if (data) { } }]; }
看代码的时候最好是从第一部分看,我为了突出重点因此把第三部分放到最前面了。基本上我就干了一件事情,把block改写为RAC。
其实用RAC改写block是不难的,难的在于block传值传了两个回去,RAC我没找到能够传两个值的地方,因而我用了RACTuplePack,这个是RAC里一个宏定义,能够打包变量。而后在信号收取端利用RACTupleUnpack(id resultData, NSError *error) = x;
来解加压缩变量(用词不许确见谅)
在这里很是感谢这样好用的ReactiveCocoa,根本停不下来这篇博客。看到了RACTuplePack这个宏定义。
在Coding的代码里,我看到若是未登陆是会弹出登陆界面。可是咱们要求是后台登陆而后从新请求。
我开始是想在get请求那部分直接在此调get的网络请求。可是不会执行两次网络请求。
以前的项目是在vc的界面判断没数据则在此调用函数。若是那样的话我不是白封装半天了...因而决定封装到network里,我就在从新登陆后判断有无数据,有数据则意味着登陆成功,登陆成功,在此调一个登陆的函数。
虽然这样看上去就不合理,可是是我能尝试出来的一个办法了...尝试了好几天,查了半天也没有相似的解决方案。
我在解决问题二的时候就出现这种问题,不能进行网络请求,我只是一个简单的调函数。可是,可是,可是,在rac里必需要subscribeNext,若是不subscribeNext则不会调!这是须要记住的。
Coding对于Get请求还作了缓存,我没作。后面会慢慢加上。
RAC是对于不少东西的一个大集合,好比block好比KVO等等。因此须要对iOS的内存管理机制进行一个深刻理解,这是我一直所欠缺的。这个问题在我解决问题二的时候出现了好几回报错,都是这个问题。可是我却没法解决。
Coding中还用到了不少显示错误的MBProgress等等,我在此都没写,若是想仔细研究去看Coding的源码。
Coding的网络请求还有不少对文件的处理,post请求等等,我如今只改写了Get和Post请求。后面须要把Coding这一套都好好的研究一下。
对于RAC的理解仍是不够,在整个过程当中遇到不少问题。
最后的最后感谢Coding将他们的客户端开源出来,感谢为Coding贡献代码的工程师。是大家让我学到了更多的东西?