FOR CM AND PLUSUB

好久没写博客了。之前一直在研究MVVM这种新的开发模式。也算是沉淀了一段时间,国庆期间可能还会写写MVVM。今天要聊的是创萌工作室的iOS客户端网络请求的封装。因为很多原因封装的还不够好存在很多问题今天写出来只是想把最近做的事情写出来作个记录。

我写这篇文章已经做好了被喷死吐槽死的准备了,因为我感觉我封装的太有问题了,但是又真的很想写一写,毕竟对我来说确实解决了一个大问题。

另外:链接不知道为啥都不变色了😭基本上我都给链接了,就凑合一下吧…

先简单的说说我们的网络请求转变过程

加入工作室一年。一共做了有三四个项目了。我先大概叙述一下我们的网络请求的转变过程

第一阶段 直接调用AFNetworking

第一个项目那时候懵懂无知,每个界面都直接调AFNetworking。这样导致的问题就是代码量骤然增加。

第二阶段 直接调用YTKNetwork

第二个项目刚做的时候唐巧开源了他们在猿题库封装的YTKNetwork。那时候还是懵懂无知,还是在每个界面都开始直接调YTKNetwork。人家封装的那么好的东西就被我用成这个栽子,简直对不起巧大。我记得有个界面好像有5条网络请求,可以想象代码的冗余度。

第三阶段 开始使用ReactiveCocoa

因为在做第二个项目的时候我和迪哥负责不同的客户端。迪哥在看了limboy的基于AFNetworking2.0和ReactiveCocoa2.1的iOS REST Client开始将网络请求剥离到一个专门的界面,这样每次就不用写很多的东西了。

limboy用了AFNetworking-RACExtensions来实现回调的效果。简单的说就是subscribe一个信号,然后信号会返回一个信号回来,这样就实现了将网络请求部分剥离的效果。

我在第二个项目基本上结束的时候上线了一个自己独立开发的app,里面就是用了这样的方法。

这学期在做项目的时候还是在沿用迪哥的代码,但是我发现了很多问题。

一是limboy在写rac代码的时候用的concat无法完成网络请求再请求。简单的说,服务器在返回告诉我session失效的时候我先需要后台自动登录然后再次网络请求。我再写的时候concat无法实现再次网络请求,我也不太明白为什么,试了各种方法都不行。这是很困惑我的我后面还需要再次研究一下。

二是再判断服务器返回的东西的时候,需要判断状态码。如果成功,那么会有json数据返回回来,如果失败则没有数据。出现这样的情况如果我在Acontroller调Bcontroller的网络请求则还需要判断是不是返回了一个数组或者对象,如果是,开始对数据进行处理,如果不是,还得重新进行网络请求,因为说明session失效了。

这样还是导致了网络请求部分有大量的代码。

根据Coding的iOS重新封装网络请求

Coding的iOS客户端是开源的,在Github和Coding官网都有。我放的链接是一个下载下来就能跑起来的。(强迫症,跑不起来的代码都不想看..不过现在看MVVM好多都跑不起来也硬着头皮看了)

Coding的网络请求自己看了。Coding是用block来进行回调的。至于这一块选择notice还是block还是delegate,可以参考iOS应用架构谈 网络层设计方案我算是认真看了,但是不是很能写的出来…

插一句话,我们为什么不用block。因为迪哥也不太会block就直接上rac了,我之前的博客写过简单的block,我在写代码的时候用delegate和notice比较多所以对block的实践比较少。这是我自身的问题。而且说实话我觉得用rac挺好的,因为block加上typedef啥的其实很多东西的,不像rac直接调就完事了。

下面开始今天的算是干货的东西

1. 分析Coding网络请求

Coding的网络请求我觉得可以总结为

block -> block -> AFNetworking

下面来解释一下,首先第一个block是我们的主viewcontroller,也就是我们逻辑部分和视图部分。首先第一个block调Coding_NetAPIManager里的函数。然后在Coding_NetAPIManager再调CodingNetAPIClient里的函数。

(具体Coding代码请看Coding客户端代码的链接)

我们倒着来说。

第三部分 AFNetworking

在我分类的AFNetworking里也就是CodingNetAPIClient里,Coding进行了一件事情,那就是进行AFNetworking的网络请求。

在获取到数据的时候的对reponse进行一个判断。在判断数据的时候,如果数据有错误,则直接显示错误的msg,如果没有错误,那么则不返回任何东西。

然后在网络请求中判断,如果有错误,那么返回nil和id类型的error。如果没错误,返回response和nil。

第二部分 block

在这部分里,回调的结果有两种,一种是有数据,一种是没数据。其实到这就行了。那么现在在这第二个部分干什么呢,json转model。就这么简单。返回的东西,如果有数据返回,那么就再次返回model或者是data和nil,如果没有数据返回,就返回nil和error。

第一部分 block

到这里,其实只要判断有无数据就可以啦。

好了。下面我们只需要用RAC来替换block就完成了。当然了,中间有坑,不会那么简单的…

2. 改写网络请求

我要上代码了。依然三部分。我们还是倒着来。我放关键的代码在这。

既然我把Coding的代码分成了

block -> block -> AFNetworking

那么我的基本上就可以说是

RAC -> RAC -> AFNetworking

也是倒着来。

第三部分 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。

3. 问题以及填坑

问题一:回调两个值

其实用RAC改写block是不难的,难的在于block传值传了两个回去,RAC我没找到可以传两个值的地方,于是我用了RACTuplePack,这个是RAC里一个宏定义,可以打包变量。然后在信号收取端利用RACTupleUnpack(id resultData, NSError *error) = x;来解加压缩变量(用词不准确见谅)

在这里非常感谢这样好用的ReactiveCocoa,根本停不下来这篇博客。看到了RACTuplePack这个宏定义。

问题二:如何在服务器返回session失效之后进行两次链接请求,一次后台自动登录,一次重新进行网络请求

在Coding的代码里,我看到如果未登录是会弹出登录界面。但是我们要求是后台登录然后重新请求。

我开始是想在get请求那部分直接在此调get的网络请求。但是不会执行两次网络请求。

之前的项目是在vc的界面判断没数据则在此调用函数。如果那样的话我不是白封装半天了…于是决定封装到network里,我就在重新登录后判断有无数据,有数据则意味着登录成功,登录成功,在此调一个登录的函数。

虽然这样看上去就不合理,但是是我能尝试出来的一个办法了…尝试了好几天,查了半天也没有类似的解决方案。

问题三:为什么有时候不会执行网络请求

我在解决问题二的时候就出现这种问题,不能进行网络请求,我只是一个简单的调函数。但是,但是,但是,在rac里必须要subscribeNext,如果不subscribeNext则不会调!这是需要记住的。

4. 还存在的问题以及不足

  1. Coding对于Get请求还做了缓存,我没做。后面会慢慢加上。

  2. RAC是对于很多东西的一个大集合,比如block比如KVO等等。所以需要对iOS的内存管理机制进行一个深入理解,这是我一直所欠缺的。这个问题在我解决问题二的时候出现了好几次报错,都是这个问题。但是我却无法解决。

  3. Coding中还用到了很多显示错误的MBProgress等等,我在此都没写,如果想仔细研究去看Coding的源码。

  4. Coding的网络请求还有很多对文件的处理,post请求等等,我现在只改写了Get和Post请求。后面需要把Coding这一套都好好的研究一下。

  5. 对于RAC的理解还是不够,在整个过程中遇到很多问题。

最后的最后感谢Coding将他们的客户端开源出来,感谢为Coding贡献代码的工程师。是你们让我学到了更多的东西😘