在现在的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. luogu P3180 [HAOI2016]地图 仙人掌 线段树合并 圆方树

    LINK:地图 考虑如果是一棵树怎么做 权值可以离散 那么可以直接利用dsu on tree+树状数组解决. 当然 也可以使用莫队 不过前缀和比较难以维护 外面套个树状数组又带了个log 套分块然后就 ...

  2. luogu P3920 [WC2014]紫荆花之恋

    LINK:紫荆花之恋 每次动态加入一个节点 统计 有多少个节点和当前节点的距离小于他们的权值和. 显然我们不能n^2暴力. 考虑一个简化版的问题 树已经给出 每次求某个节点和其他节点的贡献. 不难想到 ...

  3. python机器学习经典实例PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书

    点击获取提取码:caji 在如今这个处处以数据驱动的世界中,机器学习正变得越来越大众化.它已经被广泛地应用于不同领域,如搜索引擎.机器人.无人驾驶汽车等.Python机器学习经典实例首先通过实用的案例 ...

  4. 无所不能的Embedding 1 - Word2vec模型详解&代码实现

    word2vec是google 2013年提出的,从大规模语料中训练词向量的模型,在许多场景中都有应用,信息提取相似度计算等等.也是从word2vec开始,embedding在各个领域的应用开始流行, ...

  5. 使用ajax发送的请求实现页面跳转

    ajax 本身是不适用于页面跳转的: 可以借助其他方法实现: 1,window.location.href = "/home"; 2,springMVC 返回的modelAndVi ...

  6. 关键字Run Keyword If 如何写多个条件语句、如何在一个条件下执行多个关键字

    Run Keyword If 关键字给出的示例是: 但是,这往往不能满足我们实际需要,比如,我们需要同时判断多个条件是否成立,或者在条件成立时我们想要执行多个关键字,虽然可以进行封装再调用,但是比较麻 ...

  7. JS 移动端轮播图案例

    body { margin:; } .hearder { width: 100%; height: 150px; position: relative; } ul { list-style: none ...

  8. Linux系统安装MySQL详细教程

    首先进入MySQL官网下载rpm安装包 用yum install mysql80-community-release-el7-3.noarch.rpm 安装 yum repolist all|grep ...

  9. XCTF-WEB-高手进阶区-Web_python_template_injection-笔记

    Web_python_template_injection o(╥﹏╥)o从这里开始题目就变得有点诡谲了 网上搜索相关教程的确是一知半解,大概参考了如下和最后的WP: http://shaobaoba ...

  10. wifi渗透

    前言 本文主要讲述 家庭家庭家庭中(重要的事情说三遍,企业认证服务器的wifi一般非常非常的安全破解不来)如何破解wifi密码,破解wifi密码后的内网渗透利用(简单说明),如何设置wifi路由器更安 ...