使用AFNetworking的实践2018

网络库选哪个?

  • 以前还有纠结,比如ASI,MK之类的,现在基本上是AFNetworking

  • NSURLSection直接写,也是一个很不错的选择。特别是不适合用第三方库的情况,比如插件、SDK之类的。

  • 如果不是习惯性思维,本人更愿意用NSURLSection自己写。当然,没有真正实践过,信心还是不足的。用AFNetworking可以有效地说服自己,或者说服同事。

AFNetworking学习

  • AFNetworking网上的学习资料很多。关于原理的,这篇写的比较好。AFNetworking到底做了什么?

  • 基本使用的话,可以看GitHub上的介绍AFNetworking
    有一点不理解的是,例子都是AFURLSessionManager的,为什么不提供AFHTTPSessionManager相关的实例?

  • 如果考虑二次封装的话,可以看一下这篇文章:XMNetworking 网络库的设计与使用
    并且在GitHub上有代码可以参考: XMNetworking

如何借鉴?

  • AFNetworking基本结构如下,几个主要的类可以画出关系图。网上有很多,可以借鉴。
image.png
  • AFURLSessionManager是核心类,虽然后缀是Manager,不过并不是单例。数据,上传,下载三种业务都在这里完成。
  • AFURLSessionManager中,输入信息仍然使用URLRequest,自定义的AFHTTPRequestSerializer并没有使用。
  • AFURLSessionManager中, 安全和网络可达性都有专门的类负责,在初始化函数中一并启动。
  • 数据通讯,相对来说,还是使用AFHTTPSessionManager更方便,这里用到了AFHTTPRequestSerializer,对URLRequest做了一层封装。
  • AFHTTPSessionManager也不是单例,虽然提供了+ (instancetype)manager方法。
  • 如何封装AFNetworking,网上的这张图可以参考一下:
image.png
  • XMEngineAFNetworking提供一层简单封装;
  • XMCenter提供了一些默认参数,比如域名,和后台约定的固定头部参数等等;同时也是管理各个网络连接的中心。
  • XMRequest对系统的URLRequest做了一层封装,代表了一个网络连接,也是输入参数自定义的地方;

做什么?不做什么?

  • 做:数据业务中的POST请求。这大概占了80%左右的网络需求;

  • 可选择做:数据业务中的GET请求。除了自动网络缓存功能(系统自动提供的),实在想不出比POST优越的地方;所以一开始可以不提供,以后根据实际情况添加。

  • 做:图片上传功能,其实是AFMultipartFormData类型的POST请求。可以把UIImage也当做一种NSData看待。如果前后端协调好,甚至可以看做是普通的POST请求,只是多提供几个参数而已。至于多张图片,用一个for循环就搞定了。

  • 做:网络状况检测功能,AFURLSessionManager中的AFNetworkReachabilityManager就是做这个的,要用的时候,直接访问以下相关属性就可以了;

  • 可选择做:文件上传和下载,AFNetworking大部分的代码都是在处理这方面的代理。这个要根据业务情况,如果没有这方面的需求,就不要折腾了。当然要用也很简单,直接调用AFURLSessionManager中相关函数就可以了,麻烦的事情就让停留在AFNetworking中吧。

  • 不做:图片下载功能,一般选择SDWebImage、YYWebImage等其他第三方库来做。当然AFNetworking也有这个功能,只是一般不用而已。

  • 不做:HEAD, PATCH, DELETE, PUTHTTP请求,实际中,这些真的很少用到。

  • 不做:本地缓存,这个应该由其他第三方库,比如YYCache来做。AFNetworking中的是HTTP协议缓存,用的是系统的NSURLCache,只有GET请求有效,基本上不起作用。这部分功能放在更上一层的业务模块里面更合适一些。

  • 不做:防抖功能,和本地缓存类似,做在更高一层的业务模块中更合适一些。

  • 不做:失败重连,重连几次合适?这个很难有合适的经验值。重连次数太多,一直在“转菊花”,很有可能降低用户体验,吃力不讨好。

  • 不做:BatchRequest,ChainRequest等。这些是很酷的功能,不过用的场景不多,并且用起来并不比在业务层自己引入一个OperationQueue处理起来简单。没有隐藏相关复杂性,不如不做,让业务层自己处理反而更好。

  • 不做:证书安全之类的检查,虽然AFNetworking中的AFSecurityPolicy就是做这个的,一般只需要简单配置就能达到要求。前后端的配合是个问题,没有必要,就别折腾了。

小结:通过以上分析,第一步,可以优先提供POST请求的封装,以及网络状况查询功能;其他的功能可以延后考虑,按照项目发展情况有选择地添加。至于图片数据上传,先按照普通的POST请求对待。

如何封装AFNetworking?

  • 提供一个专门的类,比如KJTNetworkEngine进行封装,用单例的形式比较好。

  • 用子类AFHTTPSessionManager更方便,而不是像官方文档中介绍中的那样用AFURLSessionManager

  • AFJSONRequestSerializer代替默认的AFHTTPRequestSerializer。这个也是目前还不太理解的地方,既然响应用了AFJSONResponseSerializer,为什么请求参数却使用AFHTTPRequestSerializer

image.png
  • 这一层封装尽量简洁,如果设计不出更好的接口,那么就尽量用原来的,就算是直通也行。比如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变得非常简单。封装后的头文件是下面这样的,连注释,大部分都是从AFNetworkingcopy过来的:
//
//  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

  • 有些工作是不论成功失败都需要做的,比如停止转菊花,所以提供一个completionblock参数,能够带来方便。

  • 通过上面的分析,头文件可以是这样的:

//
//  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实现了一次封装,将一个功能强大,相对复杂的通用网络库,变成了一个功能单一,使用简单的网络组件,方便上层调用。

  • 文件夹的组织是这样的:

image.png
  • 以后增加上传和下载,相对比较简单,只要在KJTNetworkTask中增加两个API函数就可以了。处理回调的麻烦,AFNetworking都做了。架构定了,添加一下,相对会容易很多。

  • 至于链式请求,batch请求,本地缓存等等,更多的是一种业务概念,不合适放在组件中。放在更高一级的数据服务中实现相对而言会更好一些。

你可能感兴趣的:(使用AFNetworking的实践2018)