iOS点点滴滴

LSYNetworking:基于AFNetworking的网络请

2023-06-20  本文已影响0人  樂幽

基础篇LSYNetworking的基本使用进行了说明,进阶篇将讲解一下LSYNetworking对复杂业务场景的支持,比如大家用的最多的加解密功能,缓存功能等;

请求参数加密


如果你需要在请求之前将参数进行加密,那么你需要在你的BaseRequest里重写handleParams:这个方法,在此方法里进行加密工作;

接着上一篇中的例子,在YourBaseRequest中添加一个属性needEncrypt,用来表示当前请求的参数是否需要加密,如果你的所有请求都是需要加密的,也可以不要这个参数。

下边是handleParams:方法的实现:

- (NSDictionary *)handleParams:(NSDictionary *)params{
    if (!_needEncrypt) {
        return params;
    }
    NSDictionary *encryptedParams = params.copy;//假装这是加密操作
    return encryptedParams;
}

至此,加密功能就添加完毕了。

需要说明一下的是,这个方法是在子线程执行的,这样设计是为了避免加密操作太过耗时而占用主线程时间;

返回值解密


服务器对请求返回值的加密策略一般有两种,一种是对整个response进行加密,另一种是对responseresult部分进行加密;
这两种加密方式,除了加密的部分不一样外,还有一点需要注意的是,请求的responseType也是不一样的。
response整体进行加密,responseType需要设置成HTTP,即返回的数据是NSData类型的,要等到我们解密后,才会是json;
而只加密resultresponseType需要设置成JSON(当然,你若设置成HTTP也是没问题的,只不过这样需要做额外的工作)。

如果要将返回值解密,需要在handleResponse:方法中添加解密逻辑。

YourBaseRequest中添加两个属性,needDecryptResponseneedDecryptResult,分别用来表示是否需要解密responseresult

然后给YourBaseResponse添加“用加密的response初始化”和“用result加密的response初始化”这两个初始化方法:

- (instancetype)initWithEncryptedResponse:(id)response;

- (instancetype)initWithResultEncryptedResponse:(id)response;

然后在handleResponse:方法中添加相关逻辑:

- (id<LSYResponseProtocol>)handleResponse:(id)response{
    if (_needDecryptResponse) {
        //如果是对整个response进行加密的情况
        //顺便一说,如果是对整个response进行加密,那么responseType一定需要是http
        return [[YourBaseResponse alloc] initWithEncryptedResponse:response];
    }else {
        //如果是xml的response,需要进行xml解析
        if ([response isKindOfClass:NSXMLParser.class]) {
            response = [NSDictionary dictionaryWithXMLParser:response];
        }
        if (_needDecryptResult){
            //如果是对result进行加密的情况
            return [[YourBaseResponse alloc] initWithResultEncryptedResponse:response];
        }
        //不需要解密
        return [[YourBaseResponse alloc] initWithResponse:response];
    }
}

handleResponse:这个方法也是在子线程执行的,不用担心解密操作太过耗时卡住主线程的问题。

别忘了修改responseType

-(LSYResponseSerializerType)responseType{
    if (_needDecryptResponse) {
        return LSYResponseSerializerTypeHTTP;
    }
    return LSYResponseSerializerTypeJSON;
}

接下来是两个初始化方法的实现:

- (instancetype)initWithEncryptedResponse:(id)response{
    response = [response copy];//假装这是解密操作
    return [self initWithResponse:response];
}

- (instancetype)initWithResultEncryptedResponse:(id)response{
    self = [self initWithResponse:response];
    //对result进行解密
    self.result = [self.result copy];//假装这是解密操作
    return self;
}

返回测试数据


开发过程中,经常会遇到,接口信息已经和后台协商完毕,我们的UI部分也已经开发完毕,就等接口了,可是接口还没好。

这时候我们可以先将UI的数据写死,然后等后端开发完接口,再继续开发调试。

如果你使用了LSYNetworking,你还会有另一个选择,你可以在没有接口的情况下,返回测试数据,进行UI调试。

如果返回值的结构,字段名称,类型等,都已经和后台商量好了,那么当你用测试数据调试完毕后,基本就没有额外的开发了。

要实现测试功能,需要重写Request中的responseDataSource这个方法。假设,这里有个XXXFriendListRequest,用来获取好友信息列表,类我已经创建好,需要添加的内容也都添加完毕了,但是,没有接口!

为了测试,我创建了一个plist文件,作为数据源:


测试用的plist文件

然后,我重写了XXXFriendListRequestresponseDataSource方法:

-(id)responseDataSource{
    NSArray *friendList = [NSArray arrayWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"FriendsInfoList" ofType:@"plist"]];
    return @{
        @"resultcode":@(200),
        @"resultmsg":@"请求成功!",
        @"result":friendList
    };
}

当然,你也可以不要plist文件,直接这么写:

-(id)responseDataSource{
    return @{
        @"resultcode":@(200),
        @"resultmsg":@"请求成功!",
        @"result":@[
            @{
                @"user_id" : @0,
                @"name" : @"张三",
                @"sex" : @0,
                @"age" : @22
            },
            @{
                @"user_id" : @1,
                @"name" : @"李四",
                @"sex" : @1,
                @"age" : @20
            }
        ]
    };
}

如果返回了responseDataSource,则会跳过请求的步骤,直接使用responseDataSource作为Response返回。

数据缓存


所谓缓存,其实就是将本次请求的结果保存在本地,然后在下次请求中,读取之前存储的Response作为responseDataSource返回;

YourBaseRequest中添加一个属性shouldCache,用来表示当前请求是否需要缓存。再添加一个readonly的属性isCachedResponse,用来表示当前的请求是否使用的缓存。

然后我们需要给YourBaseResponse添加一个属性responseJson,用来存放最原始的json数据。

接下来,我们要修改YourBaseResponse的初始化方法,在初始化的时候,存储一下responseJson数据:

- (instancetype)initWithResultEncryptedResponse:(id)response{
    self = [self initWithResponse:response];
    //对result进行解密
    self.result = [self.result copy];//假装这是解密操作
    //因为涉及到缓存Response,所以需要在result解密后,将Response的result替换为解密后的数据
    NSMutableDictionary *decryptResponse = [_responseJson mutableCopy];
    if (!_result) {
        [decryptResponse removeObjectForKey:@"result"];
    }else{
        [decryptResponse setObject:_result forKey:@"result"];
    }
    _responseJson = decryptResponse.copy;
    return self;
}

- (instancetype)initWithResponse:(id)response{
    self = [super init];
    if (self) {
        _code = [[response objectForKey:@"resultcode"] integerValue];
        _msg = [response objectForKey:@"resultmsg"];
        _result = [response objectForKey:@"result"];
        
        _responseJson = response;
    }
    return self;
}

当然,我这里默认是缓存解密后的Response,如果想要缓存解密前的,可以在YourBaseRequesthandleResponse:方法中获取最原始的Response

已经有了response,接下来,我们需要重写YourBaseRequest请求成功的方法,在这个方法中缓存数据(失败了不需要缓存,如果当前已经是读取的缓存也不需要缓存):

- (void)requestSuccessWithResponseObject:(id<LSYResponseProtocol>)response task:(nonnull NSURLSessionTask *)task{
    //可以在这里处理缓存逻辑
    YourBaseResponse *res = response;
    if(_shouldCache && !_isCachedResponse) {
        //假装这是写入缓存操作
        [NSUserDefaults.standardUserDefaults setObject:[res.responseJson yy_modelToJSONString] forKey:self.url];
        [NSUserDefaults.standardUserDefaults synchronize];
    }
}

然后,在responseDataSource中读取缓存并返回:

- (id)responseDataSource{
    //可以在这里处理缓存逻辑
    if(_shouldCache) {
        //假装这是读取缓存操作
        NSDictionary *cachedResponse = [NSUserDefaults.standardUserDefaults objectForKey:self.url];
        //本地是否有已缓存的数据
        if (cachedResponse) {
            _isCachedResponse = YES;
            return cachedResponse;
        }
    }
    return nil;
}

如果缓存的是解密后的Response,则不需要再解密了,别忘了在handleResponse:方法中加个判断:

//缓存不需要解密
if (_isCachedResponse) {
    return [[YourBaseResponse alloc] initWithResponse:response];
}

自动显示/隐藏loading view


如果我们在每次请求的时候,都需要在请求之前显示loading view,在请求成功/失败之后隐藏loading view,那可能确实比较麻烦,因此,我们可以添加一个自动转菊花的功能。

我们可以在YourBaseRequest中添加一个属性showLoadingView,用来表示是否自动转菊花,然后我们在YourBaseRequest中重写startRequest方法,在此处显示loading view:

-(NSString *)startRequestWithSuccessBlock:(LSYRequestSuccessBlock)successBlock
                             failureBlock:(LSYRequestFailBlock)failureBlock{
    if (_showLoadingView) {
        //这里可以开始转菊花
    }
    return [super startRequestWithSuccessBlock:successBlock failureBlock:failureBlock];
}

然后重写requestSuccessrequestFailed方法,隐藏loading view:

- (void)requestSuccessWithResponseObject:(id<LSYResponseProtocol>)response task:(nonnull NSURLSessionTask *)task{
    if (_showLoadingView) {
        //这里可以取消转菊花
    }
}

- (void)requestFailedWithError:(NSError *)error task:(nonnull NSURLSessionTask *)task successBlock:(LSYRequestSuccessBlock _Nullable)successBlock failureBlock:(LSYRequestFailBlock _Nullable)failureBlock{
    if (_showLoadingView) {
        //这里可以取消转菊花
    }
}

这样,我们就可以在子类的init方法里进行设置,这个子类默认就会转菊花。

统一处理错误信息


可以在YourBaseRequestrequestFailed方法中处理一些公共的错误信息,如未登录,安全验证,Alert提示等:

- (void)requestFailedWithError:(NSError *)error task:(nonnull NSURLSessionTask *)task successBlock:(LSYRequestSuccessBlock _Nullable)successBlock failureBlock:(LSYRequestFailBlock _Nullable)failureBlock{
    //处理一些公共的错误
    if (error.isBusinessError) {
        //处理业务逻辑上的错误
    }else{
        //处理其他错误
    }
}

对某个请求返回值的特殊处理


举个例子,XXXFriendListRequest返回的好友列表,需要根据年龄进行排序展示。

我们可以在successBlock的回调里做这件事,但如果这个请求在多个地方调用呢?我们总不能每次都要写一遍排序代码。虽然我们可以专门写一个Util对好友信息进行排序,但这不够好。

最合适的当然是,请求最终返回的数据就是排过序的,所以我们可以重写XXXFriendListRequestrequestSuccess方法:

- (void)requestSuccessWithResponseObject:(id<LSYResponseProtocol>)response task:(nonnull NSURLSessionTask *)task{
    NSArray<XXXFirendInfo *> *firendList = response.result;
    //这里可以对firendList进行一些处理,如排序操作,写在这里可以避免每次请求都需要在请求回调里写相同的排序逻辑
    response.result = firendList.copy;//假设这是排序操作
}

当然,我们也可以修改返回值的内容,用来测试。

更多功能


可以在YourBaseRequestrequestSuccessrequestFailed方法中,处理更多逻辑,比如实现一些网络请求信息记录,网络请求埋点等debug功能。

小结


至此,LSYNetworking的使用说明就全部讲完了~我所遇到的,想到的功能都在这里了。

当然,LSYNetworking可拓展的功能不止这些,我相信,它可以处理更复杂的业务逻辑,任何属于网络框架的的业务,应该都能找到一个合适的节点进行添加,如果没有找到,希望你能告诉我,我会继续优化这个框架~

大家感兴趣的话,可以下载一下我写的 Demo,里边有一些详细的代码实现~

上一篇 下一篇

猜你喜欢

热点阅读