在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点。在这篇文章中我所提出的架构,并不是所谓的 最好 的网络请求架构,因为我只基于我这个app原有架构进行改善,更多的情况下我是以app为出发点,让这个网络架构能够在原app的环境下给我一个完美的结果,当然如果有更好的改进意见,我会很乐于尝试。

关于网络请求框架

一个好的网络请求框架对于一个团队来说是十分重要的。如果一个网络请求框架没有封装好,或者是在设计上存在问题,那么在开发上会造成许多问题,就拿这段代码作为例子:

[leaveAPI startWithCompletionBlockWith:^(BaseRequest *baseRequest, id responseObject) {
//check the response object
BOOL isSuccess = [leaveAPI validResponseObject:responseObject];
if (isSuccess) {
//do something...
}
}failure:^(BaseRequest *baseRequest) {
//do something...
}];

上面这段代码存在着不少的问题,比如把请求数据的判断放到了每一个请求中、在leaveAPI的块方法中再次调用leaveAPI、块参数中的baseRequest并没有实质作用等等……针对这些问题我会一一进行修正。

不要让其他人做请求数据有效与否的判断

在上面的代码中,对resposeObject是否有效的判断被设计成了BaseRequest类中的一个方法,程序员需要在调用网络请求后,再调用该方法对responseObject进行判断,这样的设计存在很大的弊端。

在实际应用中,很多时候程序员在调用网络请求后往往会忘记调用该方法对返回结果进行判断,甚至忘记了存在这个方法,自行对responseObject进行判断。首先这造成了大规模的代码重复,另一方面,不同程序员自己编写的判断方法散落在各个请求中,假如app在日后更新过程中改变了这个判断标准,会给修改带来很大困难。

注意在块方法中的循环调用

上面的代码中,在leaveAPI的块方法中,再次调用了leaveAPI中的方法,这样导致了"retain cycle",实际上正确的调用方法应该是:

[leaveAPI startWithCompletionBlockWith:^(LeaveAPI *api, id responseObject) {
//check the response object
BOOL isSuccess = [api validResponseObject:responseObject];
if (isSuccess) {
//do something...
}
}];

为什么会出现这样的情况,首先主要是因为整个请求框架的注释不清晰,导致其他程序员对方法的理解存在偏差,进而天马行空,发挥自己的想象力来调用方法。另外由于各个API与BaseRequest的设计上存在问题,导致整个网络请求框架的混乱。

不要在单独的API中实现上传下载操作

在旧的网络请求框架中,BaseRequest一开始的设计中并没有针对上传和下载操作进行处理,而且整个BaseRequest的设计中并没有AOP,这个导致了在日后需要增加上传和下载功能的时候只能将他们写到单独的API中,这个导致了代码重复,代码的复用性降低,如:

//
// FileAPI.m
// ...some methods... #pragma mark - Upload & Download -(void)uploadFile:(FileUploadCompleteBlock)uploadBlock errorBlock:(FileUploadFailBlock)errorBlock {
NSString *url = self.url
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.operationQueue.maxConcurrentOperationCount = ;
manager.requestSerializer.timeoutInterval = ;
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html",@"text/json",@"text/javascript",@"text/plain",nil]; [manager POST:url parameters:[self requestArgument] constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { // upload operation ... }success:^(AFHTTPRequestOperation *operation, id responseObject) { // do something ...
}failure:^(AFHTTPRequestOperation *operation, NSError *error) { // do something ...
}];
}

FileAPI.m中,上传操作是这样实现的。写下这段代码的时候是使用AFNetworking 2.0,而现在使用的是AFNetworking 3.0AFHTTPRequestOperationManager也变成了AFHTTPSessionManger,这个时候散落在各个API的上传方法修改起来就变的很麻烦。

BaseRequest中的设计缺陷

在上文中一直在指出各个API中的缺陷,而也提到很多地方是归咎于BaseReuqest的问题,现在就来看一下它里面的一些缺陷:

首先在整个BaseRequest中,它包括了地址的组装、网络环境的判断、请求的发送等等,基本网络请求的所有操作都是由这一个类来实现。这样就导致了整个类十分庞大,在需要添加新的请求类型如我上文提到的上传与下载时,会难以下手,这就导致了我上文提到的种种问题。

另一方面BaseRequest中没有针对返回数据的处理,这里的处理是指返回数据的缓存操作、数据过滤操作、请求数据为空的处理操作等等,如果这些问题都交给方法调用者来完成的话,会导致某一模块的代码量暴涨(在本app是VC),而且很多时候数据需要的只是一个默认的缓存操作、默认的过滤操作,这个时候重复性的代码会很多,倒不如把这些操作统一处理好,假如有特殊的API需要进行特殊的配置,再由该API对这些配置进行修改,而不需要把这些默认操作交由其他程序员来完成。

我是如何设计新的网络请求框架

上文提到了各种各样的不足,所以是时候针对这些不足进行改进了。

先看大局,再看细节。首先是整个架构的数据流向:

整个网络请求框架中最重要的是其中的NetworkManage,它主要是负责整个请求的处理。

设计中的一些关注重点

首先检测网络状态

当一个请求发起的时候,首先它会检测网络是否联通,假如没有联通的时候会直接弹出一个窗口提醒用户需要先连接网络,而不会进行下一步的请求。而在旧的网络请求框架中,很多时候把这段代码放到了vc,现在将它整合进来。

- (void)addRequest:(BaseRequest*)request {

    //TODO: 检查网络是否通畅
if(![self checkNetworkConnection])
{
[self showNetworkAlertForRequest:request];
return;
}

[self checkNetworkConnection]:

- (BOOL)checkNetworkConnection
{
struct sockaddr zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sa_len = sizeof(zeroAddress);
zeroAddress.sa_family = AF_INET; SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
SCNetworkReachabilityFlags flags; BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability); if (!didRetrieveFlags) {
printf("Error. Count not recover network reachability flags\n");
return NO;
} BOOL isReachable = flags & kSCNetworkFlagsReachable;
BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
return (isReachable && !needsConnection) ? YES : NO;
}

活性组装请求地址

而在进行完网络联通的判断之后,就会对请求的地址进行组装。组装地址的方法并没有太大的变化,但是在旧的请求框架开发的时候,我注意到一个问题:在增加新需求增加新的接口的时候,往往需要连接到测试服务器上进行调试,这时候就需要将请求的地址改成测试服务器的地址。但这往往引发一些问题,因为测试服务器上可能没有正式服务器的一些数据,在测试时往往没有问题,但是转移到正式服务器上就出现了各种问题,所以我就想能不能改成程序员可以改变API连接的地址,而不改变全局的请求框架,让各个API在请求的时候判断自己是否需要连接到测试服务器。

- (NSString *)urlString{
NSString *url = nil;
//TODO: 使用副地址
if ([self.child respondsToSelector:@selector(useViceUrl)] && [self.child useViceUrl]){
baseUrl = self.config.viceBaseUrl;
}
//TODO: 使用主地址
else{
baseUrl = self.config.mainBaseUrl;
}
}

让API能够独立配置

组装地址完毕之后,就开始根据API自身的设置来进行配置,在旧的请求框架中,API的是直接继承自BaseRequest这个类,导致了BaseRequest需要完成大量的工作,或是存有大量空方法,可读性与稳定性都很差,很多东西也没有办法让API自己进行独立设置。在新的框架中,我选择将API的设置通过一个叫做APIProtocol的协议来完成,API需要配置的内容可以通过实现该协议的方法来进行配置,否则就会直接使用默认配置

//TODO: 检查是否使用自定义超时时间
if ([request respondsToSelector:@selector(requestTimeoutInterval)]) {
self.manager.requestSerializer.timeoutInterval = [request requestTimeoutInterval];
}
else{
self.manager.requestSerializer.timeoutInterval = 60.0;
} more methods ...

完善返回数据的基础判断

最后在进行完请求判断后,将会对responseObject的有效性进行判断。关于数据的判断我一开始是打算放在BaseRequest中的,因为一开始的想法是希望能够在BaseRequest中做一个默认的判断,假如API自身需要再度对responseObject进行进一步的判断时,可以通过协议方法来重新编写该API独立的判定方法。但这种方法最终被我弃用了,首先responseObject的基础判断在我看来是不应该放在BaseRequest中的,因为BaseRequest是作为一个请求的”中心”,不应该把数据处理的问题交给它处理。另一方面是因为我们需要设计的是基础判断,它和各个API独立的判断方式不是平行关系,而是层次关系,因为在设计的是每一个API都需要进行的判断,假如在整个app中有很多API需要进行独立判断,就意味着需要编写很多次基础判断逻辑,同时假如在日后需要修改这个基础判断内容,代码也散落在各个地方,这不是我们想要的结果。

所以在设计上我最终把这个判断方法放到了NetworkConfig中,新增了一个BaseFilter类,专门用于返回数据的判断,假如我的API需要增加独特的判断方法时,可以直接在请求方法中直接对responseObject进行进一步判断。

NetworkConfig.m:

//NetworkManage.m

if([self.networkConfig.baseFilter validResponseObject:responseObject])
{
request.responseObject = responseObject;
[self handleSuccessRequest:task];
}
else
{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
request.responseObject = responseObject;
[self handleFailureRequest:task error:error];
}

BaseFilter.m

@implementation BaseFilter

- (BOOL)validResponseObject:(id)responseObject
{
//TODO: 检查是否返回了数据且数据是否正确
if (!responseObject && ![responseObject isKindOfClass:[NSDictionary class]] && ![responseObject[@"success"] boolValue]) {
return NO;
}
else
return YES;
} @end

结语

我相信在软件设计中并不存在最好或者是最正确的架构,因为这是一个很抽象的工作,但我相信我们应该可以设计出一个扩展性良好和简单明了的架构,能够让新加入的程序员快速上手,能够适应软件接下来的开发需要,那这大概是一个好的架构。
以上内容就是本篇的全部内容以上内容希望对你有帮助,有被帮助到的朋友欢迎点赞,评论。
如果对软件测试、接口测试、自动化测试、面试经验交流。感兴趣可以关注我,我们会有同行一起技术交流哦。

2020,最新APP重构:网络请求框架的更多相关文章

  1. App 组件化/模块化之路——如何封装网络请求框架

    App 组件化/模块化之路——如何封装网络请求框架 在 App 开发中网络请求是每个开发者必备的开发库,也出现了许多优秀开源的网络请求库.例如 okhttp retrofit android-asyn ...

  2. android翻译应用、地图轨迹、视频广告、React Native知乎日报、网络请求框架等源码

    Android精选源码 android实现高德地图轨迹效果源码 使用React Native(Android和iOS)实现的 知乎日报效果源码 一款整合百度翻译api跟有道翻译api的翻译君 RxEa ...

  3. Android 网络请求框架android-async-http问题

    今天通过接口请求服务器的一些app数据,发现一个很奇怪的问题,请求一个链接的时候,通常在第一次请求发起的时候没有什么问题,能很快的拿到数据,但是 往后再去请求的时候就会等待很久,而且最后会请求失败,一 ...

  4. Android网络请求框架AsyncHttpClient实例详解(配合JSON解析调用接口)

    最近做项目要求使用到网络,想来想去选择了AsyncHttpClient框架开进行APP开发.在这里把我工作期间遇到的问题以及对AsyncHttpClient的使用经验做出相应总结,希望能对您的学习有所 ...

  5. 一步步搭建Retrofit+RxJava+MVP网络请求框架(二),个人认为这次封装比较强大了

    在前面已经初步封装了一个MVP的网络请求框架,那只是个雏形,还有很多功能不完善,现在进一步进行封装.添加了网络请求时的等待框,retrofit中添加了日志打印拦截器,添加了token拦截器,并且对Da ...

  6. XDroidRequest网络请求框架,新开源

    XDroidRequest 是一款网络请求框架,它的功能也许会适合你.这是本项目的第三版了,前两版由于扩展性问题一直不满意,思考来 思考去还是觉得Google的Volley的扩展性最强,于是借鉴了Vo ...

  7. 安卓开发常用网络请求框架OkHttp、Volley、XUtils、Retrofit对比

    网络请求框架总结1.xutils     此框架庞大而周全,这个框架可以网络请求,同时可以图片加载,又可以数据存储,又可以 View 注解,使用这种框架很方便,这样会使得你整个项目对它依赖性太强,万一 ...

  8. Flutter学习(7)——网络请求框架Dio简单使用

    原文地址: Flutter学习(7)--网络请求框架Dio简单使用 | Stars-One的杂货小窝 Flutter系列学习之前都是在个人博客发布,感兴趣可以过去看看 网络请求一般APP都是需要的,在 ...

  9. Android网络请求框架

    本篇主要介绍一下Android中经常用到的网络请求框架: 客户端网络请求,就是客户端发起网络请求,经过网络框架的特殊处理,让后将请求发送的服务器,服务器根据 请求的参数,返回客户端需要的数据,经过网络 ...

随机推荐

  1. PHP empty() 函数

    empty() 函数用于检查一个变量是否为空.高佣联盟 www.cgewang.com empty() 判断一个变量是否被认为是空的.当一个变量并不存在,或者它的值等同于 FALSE,那么它会被认为不 ...

  2. luogu P3217 [HNOI2011]数矩形

    LINK:数矩形 题意:给出n个点 求出一个最大的矩形. 矩形可以使斜着的.(不会告诉你样例我算了几年 这道题的一个潜规则 矩形面积都是整数 我也不知道为啥一定是整数 姑且是题目输出的要求吧. 所以用 ...

  3. electron开发 - 打印流程(仅支持6.0.0版本以上)

    Electron打印 标签打印 标签打印一般有两种方式: 驱动打印,与普通打印机一样通过驱动方式打印. 通过指令打印,不同厂家的的打印机指令集不一样,可查看厂家提供的手册. electron 打印方式 ...

  4. python数据处理PDF高清电子书

    点击获取提取码:jzgv 内容简介 本书采用基于项目的方法,介绍用Python完成数据获取.数据清洗.数据探索.数据呈现.数据规模化和自动化的过程.主要内容包括:Python基础知识,如何从CSV.E ...

  5. json-lib无法下载

    maven无法下载json-lib 配置一下这个 <classifier>jdk15</classifier> 因为远程提供了两个

  6. time模块 random模块

    time模块 time.sys等模块是C语言实现的,内置到了python解释器的.而不是py文件. 导入模块的时候,优先到python解释器,然后才会找py文件. #时间戳 #计算 # print(t ...

  7. 前端面试基础题:Ajax原理

    Ajax 的原理简单来说是在⽤户和服务器之间加了—个中间层( AJAX 引擎),通过XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后⽤ javascrip t 来操作 D ...

  8. 重温这几个屌爆的Python技巧!

    我已经使用Python编程有多年了,即使今天我仍然惊奇于这种语言所能让代码表现出的整洁和对DRY编程原则的适用.这些年来的经历让我学到了很多的小技巧和知识,大多数是通过阅读很流行的开源软件,如Djan ...

  9. 1)uboot的编译和烧写

    购买荔枝派ZERO已经将近一个星期了,由于官方资料不够完整一直没有任何进展.经过今夜近三个小时的折腾终于将UBOOT烧写成功,现将过程记录如下: 1)获取官方uboot 源码 : git clone  ...

  10. ASP.NET Core3.x 基础(1)

    ASP.NET Core与2.x相比发生的一些变化: 项目结构 Blazor SignalR gRPC 关于Program类:Main方法,在系统执行时就会找到这个Main方法,实际上是配置了ASP. ...