网络库选哪个?
以前还有纠结,比如
ASI,MK
之类的,现在基本上是AFNetworking
用
NSURLSection
直接写,也是一个很不错的选择。特别是不适合用第三方库的情况,比如插件、SDK
之类的。如果不是习惯性思维,本人更愿意用
NSURLSection
自己写。当然,没有真正实践过,信心还是不足的。用AFNetworking
可以有效地说服自己,或者说服同事。
AFNetworking学习
AFNetworking
网上的学习资料很多。关于原理的,这篇写的比较好。AFNetworking到底做了什么?基本使用的话,可以看
GitHub
上的介绍AFNetworking
有一点不理解的是,例子都是AFURLSessionManager
的,为什么不提供AFHTTPSessionManager
相关的实例?如果考虑二次封装的话,可以看一下这篇文章:XMNetworking 网络库的设计与使用
并且在GitHub
上有代码可以参考: XMNetworking
如何借鉴?
-
AFNetworking
基本结构如下,几个主要的类可以画出关系图。网上有很多,可以借鉴。
AFURLSessionManager
是核心类,虽然后缀是Manager
,不过并不是单例。数据,上传,下载三种业务都在这里完成。AFURLSessionManager
中,输入信息仍然使用URLRequest
,自定义的AFHTTPRequestSerializer
并没有使用。AFURLSessionManager
中, 安全和网络可达性都有专门的类负责,在初始化函数中一并启动。- 数据通讯,相对来说,还是使用
AFHTTPSessionManager
更方便,这里用到了AFHTTPRequestSerializer
,对URLRequest
做了一层封装。AFHTTPSessionManager
也不是单例,虽然提供了+ (instancetype)manager
方法。
- 如何封装
AFNetworking
,网上的这张图可以参考一下:
XMEngine
对AFNetworking
提供一层简单封装;XMCenter
提供了一些默认参数,比如域名,和后台约定的固定头部参数等等;同时也是管理各个网络连接的中心。XMRequest
对系统的URLRequest
做了一层封装,代表了一个网络连接,也是输入参数自定义的地方;
做什么?不做什么?
做:数据业务中的
POST
请求。这大概占了80%左右的网络需求;可选择做:数据业务中的
GET
请求。除了自动网络缓存功能(系统自动提供的),实在想不出比POST
优越的地方;所以一开始可以不提供,以后根据实际情况添加。做:图片上传功能,其实是
AFMultipartFormData
类型的POST
请求。可以把UIImage
也当做一种NSData
看待。如果前后端协调好,甚至可以看做是普通的POST
请求,只是多提供几个参数而已。至于多张图片,用一个for
循环就搞定了。做:网络状况检测功能,
AFURLSessionManager
中的AFNetworkReachabilityManager
就是做这个的,要用的时候,直接访问以下相关属性就可以了;可选择做:文件上传和下载,
AFNetworking
大部分的代码都是在处理这方面的代理。这个要根据业务情况,如果没有这方面的需求,就不要折腾了。当然要用也很简单,直接调用AFURLSessionManager
中相关函数就可以了,麻烦的事情就让停留在AFNetworking
中吧。不做:图片下载功能,一般选择
SDWebImage、YYWebImage
等其他第三方库来做。当然AFNetworking
也有这个功能,只是一般不用而已。不做:
HEAD, PATCH, DELETE, PUT
等HTTP
请求,实际中,这些真的很少用到。不做:本地缓存,这个应该由其他第三方库,比如
YYCache
来做。AFNetworking
中的是HTTP
协议缓存,用的是系统的NSURLCache
,只有GET
请求有效,基本上不起作用。这部分功能放在更上一层的业务模块里面更合适一些。不做:防抖功能,和本地缓存类似,做在更高一层的业务模块中更合适一些。
不做:失败重连,重连几次合适?这个很难有合适的经验值。重连次数太多,一直在“转菊花”,很有可能降低用户体验,吃力不讨好。
不做:
BatchRequest,ChainRequest
等。这些是很酷的功能,不过用的场景不多,并且用起来并不比在业务层自己引入一个OperationQueue
处理起来简单。没有隐藏相关复杂性,不如不做,让业务层自己处理反而更好。不做:证书安全之类的检查,虽然
AFNetworking
中的AFSecurityPolicy
就是做这个的,一般只需要简单配置就能达到要求。前后端的配合是个问题,没有必要,就别折腾了。
小结:通过以上分析,第一步,可以优先提供
POST
请求的封装,以及网络状况查询功能;其他的功能可以延后考虑,按照项目发展情况有选择地添加。至于图片数据上传,先按照普通的POST
请求对待。
如何封装AFNetworking?
提供一个专门的类,比如
KJTNetworkEngine
进行封装,用单例的形式比较好。用子类
AFHTTPSessionManager
更方便,而不是像官方文档中介绍中的那样用AFURLSessionManager
。用
AFJSONRequestSerializer
代替默认的AFHTTPRequestSerializer
。这个也是目前还不太理解的地方,既然响应用了AFJSONResponseSerializer
,为什么请求参数却使用AFHTTPRequestSerializer
- 这一层封装尽量简洁,如果设计不出更好的接口,那么就尽量用原来的,就算是直通也行。比如POST请求的封装如下:
// 这里只是对AFNetworking的原始封装,不做处理,只是去掉了progress参数
- (nullable NSURLSessionDataTask *)postWithUrl:(NSString *)urlString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure {
NSURLSessionDataTask *task = [self.sessionManager POST:urlString parameters:parameters progress:nil success:success failure:failure];
// 用url来标识一个网络请求;
task.taskDescription = [urlString copy];
return task;
}
- 这里的
self.sessionManager = [AFHTTPSessionManager manager];
POST
的名字感觉不好,改成了postWithUrl
progress
这个block
参数感觉在POST请求中用处不大,去掉了。- 一般都是用一个整数作为
NSURLRequest
来代表一次网络请求,感觉不是很直观。不如用urlSting
,放在一个NSURLSessionTask
更加直观。
- 根据上面的需求分析,目前只需要网络请求,网络检测,超时设置,头部信息设置等功能。所以,只封装需要,就可以将强大复杂的
AFNetworking
变得非常简单。封装后的头文件是下面这样的,连注释,大部分都是从AFNetworking
中copy
过来的:
//
// KJTNetworkEngine.h
// HaiLeBao
//
// Created by zxs on 2018/7/25.
// Copyright © 2018年 KJT. All rights reserved.
//
#import
@interface KJTNetworkEngine : NSObject
# pragma mark - 单例
/**
Returns the default shared singleton object.
*/
+ (instancetype)sharedEngine;
# pragma mark - 网络请求
/**
* POST网络请求
*
* @param urlString 字符形式的URL
* @param parameters 参数字典
* @param success 成功(response)
* @param failure 失败(response,error)
*/
- (nullable NSURLSessionDataTask *)postWithUrl:(NSString *)urlString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
#pragma mark - 网络状况检测
/**
Whether or not the network is currently reachable.
*/
- (BOOL)isReachable;
/**
Whether or not the network is currently reachable via WWAN.
*/
- (BOOL)isReachableViaWWAN;
/**
Whether or not the network is currently reachable via WiFi.
*/
- (BOOL)isReachableViaWiFi;
# pragma mark - NSURLRequest参数配置
/**
The timeout interval, in seconds, for created requests. The default timeout interval is 60 seconds.
*/
- (void)changeTimeoutIntervel:(NSTimeInterval)timeoutIntervel;
/**
Sets the value for the HTTP headers set in request objects made by the HTTP client. If `nil`, removes the existing value for that header.
@param field The HTTP header to set a default value for
@param value The value set as default for the specified header, or `nil`
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(NSString *)field;
@end
如何封装公共参数?
提供一个专门的类,比如
KJTNetworkCenter
进行封装,用单例的形式比较好。AFNetworking
对这个类是不可见的,所有具体事,都交给封装层KJTNetworkEngine
来做。需求1:实际使用中,
urlString
是通过域名和path
两部分拼接而成的。并且域名一般分为开发、测试、预发、生产四种,需要根据不同的需要进行域名切换。需求2:超时时间是设置在每个请求的
NSURNRequest
中,使用时,大多数用同一个值,可以考虑在这里集中设置。需求3:头部信息一般是公共的,可以考虑在这里设置。
需求4:有一些公共参数,比如平台类型、版本号之类,可以考虑在这里设置。
实现以上需求,头文件可以是这样的:
//
// KJTNetworkCenter.h
// HaiLeBao
//
// Created by zxs on 2018/7/25.
// Copyright © 2018年 KJT. All rights reserved.
//
#import
#import "KJTNetworkConst.h"
@interface KJTNetworkCenter : NSObject
#pragma mark - 单例
/**
Returns the default shared singleton object.
*/
+ (instancetype)sharedCenter;
#pragma mark - api
/**
切换服务器域名
* @param networkType 服务器类型
*/
- (void)switchServerDomain:(KJTNetworkType)networkType;
/**
修改超时时间,全局有效,单位是秒,默认是25秒
* @param timeoutIntervel 超时时间
*/
- (void)changeTimeoutIntervel:(NSTimeInterval)timeoutIntervel;
/**
添加公共的HTTP头部信息.
@param field 字典的key
@param value 对应的值
*/
- (void)addCommonHeaderWithValue:(NSString *)value andField:(NSString *)field;
/**
添加公共参数.
@param key 字典的key
@param object 对应的值
*/
- (void)addCommonParameterWithObject:(id)object andKey:(NSString *)key;
#pragma mark - api 模块内部使用
/**
将服务器域名和api路径拼接成完成的url
@param pathString /api/后面的路径
*/
- (NSString *)urlStringWithApiPath:(NSString *)pathString;
/**
将公共参数,额外参数合并成完整的参数
@param additionalParameters 公共参数以外的额外参数,可以为nil
*/
- (NSDictionary *)allParametersWithAdditional:(nullable NSDictionary *)additionalParameters;
@end
如何实现POST请求?
提供一个专门的类,比如
KJTNetworkTask
来代表一个POST
请求session, engine, center
在语义上都只有一个,用单例实现很合适。一个session
可以对应多个POST
请求,并没有单例的概念,所以KJTNetworkTask
应该设计成普通的实例。头部信息由
KJTNetworkCenter
统一设置,这里不需要操心。超时时间由
KJTNetworkCenter
统一设置,这里不再重复设置。公共参数由
KJTNetworkCenter
统一提供,这里只需要提供额外参数就可以了。当然,额外参数也是可以没有的,那就给个nil
。域名保存在
KJTNetworkCenter
里面,所以这里只要提供后面的pathString
就可以了,可以调用KJTNetworkCenter
提供的函数,拼接成完整的urlString
有些工作是不论成功失败都需要做的,比如停止转菊花,所以提供一个
completion
的block
参数,能够带来方便。通过上面的分析,头文件可以是这样的:
//
// KJTNetworkTask.h
// HaiLeBao
//
// Created by zxs on 2018/7/25.
// Copyright © 2018年 KJT. All rights reserved.
//
#import
#import "KJTNetworkConst.h"
@interface KJTNetworkTask : NSObject
/**
* POST网络请求
*
* @param pathString 域名/api/之后的路径
* @param additionalParameters 公共参数以外的额外参数
* @param success 成功(dataDictionary)
* @param failure 失败(code, message)
* @param completion 完成(response, error)
*/
- (nullable NSURLSessionDataTask *)postWithApiPath:(NSString *)pathString additionalParameters:(nullable NSDictionary *)additionalParameters success:(nullable KJTNetworkSuccessBlock)success failure:(nullable KJTNetworkFailureBlock)failure completion:(nullable KJTNetworkCompletionBlock)completion;
@end
如何提供接口文件?
POST
请求在KJTNetworkTask
中,普通实例方式调用公共参数在
KJTNetworkCenter
中,单例方式调用网路状况检测在
KJTNetworkEngine
中,单例方式调用将用户要用的函数接口都统一到一个文件中,比如
KJTNetworking.h
,统一采用静态函数方式进行调用,会方便很多。
//
// KJTNetworking.h
// HaiLeBao
//
// Created by zxs on 2018/7/25.
// Copyright © 2018年 KJT. All rights reserved.
//
#import
#import "KJTNetworkConst.h"
@interface KJTNetworking : NSObject
/**
* POST网络请求
*
* @param pathString 域名/api/之后的路径
* @param additionalParameters 公共参数以外的额外参数
* @param success 成功(dataDictionary)
* @param failure 失败(code, message)
* @param completion 完成(response, error)
*/
+ (void)postWithApiPath:(NSString *)pathString additionalParameters:(nullable NSDictionary *)additionalParameters success:(nullable KJTNetworkSuccessBlock)success failure:(nullable KJTNetworkFailureBlock)failure completion:(nullable KJTNetworkCompletionBlock)completion;
/**
切换服务器域名
* @param networkType 服务器类型
*/
+ (void)switchServerDomain:(KJTNetworkType)networkType;
/**
修改超时时间,全局有效,单位是秒,默认是25秒
* @param timeoutIntervel 超时时间
*/
+ (void)changeTimeoutIntervel:(NSTimeInterval)timeoutIntervel;
/**
添加公共的HTTP头部信息.
@param field 字典的key
@param value 对应的值
*/
+ (void)addCommonHeaderWithValue:(NSString *)value andField:(NSString *)field;
/**
添加公共参数.
@param key 字典的key
@param object 对应的值
*/
+ (void)addCommonParameterWithObject:(id)object andKey:(NSString *)key;
/**
网络是否通.
*/
+ (BOOL)isReachable;
/**
是否连接WiFi.
*/
+ (BOOL)isReachableViaWiFi;
@end
小结:
通过以上几个步骤,就对
AFNetworking
实现了一次封装,将一个功能强大,相对复杂的通用网络库,变成了一个功能单一,使用简单的网络组件,方便上层调用。文件夹的组织是这样的:
以后增加上传和下载,相对比较简单,只要在
KJTNetworkTask
中增加两个API
函数就可以了。处理回调的麻烦,AFNetworking
都做了。架构定了,添加一下,相对会容易很多。至于链式请求,
batch
请求,本地缓存等等,更多的是一种业务概念,不合适放在组件中。放在更高一级的数据服务
中实现相对而言会更好一些。