在现在的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 vsprintf() 函数

    实例 把格式化字符串写入变量中: <?php高佣联盟 www.cgewang.com$number = 9;$str = "Beijing";$txt = vsprintf( ...

  2. Netty(一):server启动流程解析

    netty作为一个被广泛应用的通信框架,有必要我们多了解一点. 实际上netty的几个重要的技术亮点: 1. reactor的线程模型; 2. 安全有效的nio非阻塞io模型应用; 3. pipeli ...

  3. day2. 六大基本数据类型简介

    一.基本数据类型 Number 数字类型 (int float bool complex) str 字符串类型 list 列表类型 tuple 元组类型 set 集合类型 dict 字典类型 二.Nu ...

  4. vue cli4构建基于typescript的vue组件并发布到npm

    基于vue cli创建一个vue项目 首先安装最新的vue cli脚手架, npm install --global @vue/cli npm WARN optional SKIPPING OPTIO ...

  5. Pytest单元测试框架:插件-allure-pytest环境搭建并在本地生成一个测试报告

    之前写了allure-pytest的官方文档啃的内容,有些交流的朋友,实践起来没什么头绪,所以就有了这篇文章,也给自己填个坑 第一步:搭建Allure.JDK环境 1. 搭建JDK环境 不装jdk你会 ...

  6. ios 淘宝评论详情、朋友圈布局masony实现

    最近做项目,用到了类似于淘宝的朋友圈的功能,然后自己抽出了一个小demo,与大家分享 介绍:用的是masony布局的cell这样的话,文本,以及图片可以自适应,不用人工再去计算高度,很方便. 注:该d ...

  7. ios 浅谈一下UITextFiled UITextView 在tableview的cell上边展示

    最近在项目中.要做到在tableview的cell上边加一个输入框.允许用户输入. 1.我首先选的是在uitextView  然后在通知键盘出现的时候,将tableview的内容设置在键盘的上边.但是 ...

  8. 面试被问:如果系统 CPU 突然飙升且 GC 频繁,你该如何排查?

    出自:开源中国 原文:系统运行缓慢,CPU 100%,以及Full GC次数过多问题的排查思路 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题. ...

  9. Android Studio--家庭记账本(一)

    今天通过观看视频,根据老师所讲内容,编译代码.实现了Android Studio记账本里面的增加功能 源代码如下: CostBean.java: package com.example.family; ...

  10. 群晖系统如何通过Video Station套件管理NAS中的视频

    一.PC端观看视频 1.在NAS套件中心找到Video Station套件,安装套件 2.设置video套件别名,便于后期使用,控制面板----应用程序门户----video Station 3.选中 ...