iOS 网络(2)——AFNetworking

《iOS 网络——NSURLSession》中,我们介绍了 NSURLSession 的架构及工作原理。本文,我们通过阅读 AFNetworking 源代码(版本号:2.6.3),来介绍其设计架构。

AFNetwoking 概述

AFNetworking 是一套适用于 iOS、macOS、watchOS、tvOS 的网络库。AFNetworking 2.0 之后的版本构建在基于 NSURLSession 的 Foundation URL 加载系统之上。AFNetworking 扩展了 Cocoa 内置的强大的高级网络抽象,采用模块化设计,功能丰富,是最广泛使用的开源项目之一。

AFNetworking 架构

AFNetworking 框架主要包含两部分:

  • AFNetworking 核心功能
  • UIKit+AFNetworking 分类功能

这里我们主要介绍 AFNetworking 的核心功能,其整体的架构如下图所示。AFNetworking 主要包含了 6 个类:

  • AFURLSessionManager:AFNetworking 的核心类。
  • AFHTTPSessionManagerAFURLSessionManager 的子类,主要用于 HTTP 请求。
  • AFURLRequestSerialization:请求序列化器,用于将参数编码为查询字符串、HTTP 正文,并根据需要设置合适的 HTTP 头部字段。
  • AFURLResponseSerialization:响应序列化器,用于将数据解码为对象,还可以对传入的响应和数据进行验证。
  • AFSecurityPolicy:通过安全连接评估服务器对固定的 X.509 证书和公钥的信任。
  • AFNetworkReachabilityManager:监视网络可达性。

下面我们来介绍一下 AFNetworking 的核心——AFURLSessionManager

AFURLSessionManager

下图所示为 AFURLSessionManager 的内部结构。下面,我们将以该结构图为指导,介绍 AFURLSessionManager 的工作原理。

持有属性

我们首先浏览一下 AFURLSessionManager 所持有的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// 外部公开的属性
@interface AFURLSessionManager
/// 管理的 Session
@property (readonly, nonatomic, strong) NSURLSession *session;
/// 代理回调执行的队列
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
/// 使用 dataTaskWithRequest:success:failure: 创建,使用 GET 或 POST 执行的 data task 的响应。默认值是 AFJSONResponseSerializer
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
/// Session 使用的安全策略,用于评估服务器对安全连接的信任程度。AFURLSessionManager 使用默认策略 defaultPolicy
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
/// 网络可达性管理器。AFURLSessionManager 默认使用 sharedManager
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;

/// ---------------------

/// 被管理 session 当前运行的所有类型的任务,如:data,upload,download 等
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
/// 被管理 session 当前运行的 data 任务
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
/// 被管理 session 当前运行的 upload 任务
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
/// 被管理 session 当前运行的 download 任务
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;
/// completionBlock 执行所在的队列。如果是 NULL,则在主线程执行
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
/// completionBlock 执行所在的 group。如果是 NULL,则在一个私有的 group 中执行
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// 内部私有的属性
@interface AFURLSessionManager ()
@property (readwrite, nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration;
@property (readwrite, nonatomic, strong) NSOperationQueue *operationQueue;
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier;
@property (readonly, nonatomic, copy) NSString *taskDescriptionForSessionTasks;
@property (readwrite, nonatomic, strong) NSLock *lock;

/// ---------------------

@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession AF_API_UNAVAILABLE(macos);
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidFinishCollectingMetricsBlock taskDidFinishCollectingMetrics;
#endif
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
@end

初始化

首先,AFURLSessionManager 对象持有一个 NSURLSessionConfiguration 对象(会话配置对象)。使用会话配置对象即可初始化并持有一个 NSURLSession 对象(会话对象)。会话对象的初始化过程如下代码所示:

1
2
3
4
5
6
7
8
- (NSURLSession *)session {
@synchronized (self) {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
}
}
return _session;
}
注意,初始化会话对象后,会话对象将 AFURLSessionManager 设置成自己的代理。因此,AFURLSessionManager 需要实现相关协议所声明的那些方法。这些协议有:

  • NSURLSessionDelegate
  • NSURLSessionTaskDelegate
  • NSURLSessionDataDelegate
  • NSURLSessionDownloadDelegate

为了能够让开发者实现自定义处理会话级事件和任务级事件,AFURLSessionManager 提供了大量设置回调的方法,基本可以覆盖到协议所声明的每一个方法。这些回调被 AFURLSessionManager 在内部持有,只有在对应的协议方法被调用时,回调方法才会被执行。

进度管理

AFURLSessionManager 作为会话对象的代理,基本可以处理所有任务对象。但是,对于上传任务、下载任务进行进度管理,使用 AFURLSessionManager 来管理是较为繁琐的,也是不合理的。主要有以下几点原因:

  • 在上传任务和下载任务的整个生命周期中都需要持有一个进度对象 NSProgress
  • 在下载任务的整个生命周期中还需要持有一个接收数据对象,用于持续接收数据。
  • AFURLSessionManager 主要用于管理会话,根据职责单一原则,它不应该管理任务对象的具体细节。

因此,AFNetworking 提供了 AFURLSessionManagerTaskDelegate 这样一个类来管理特定任务的进度、数据。

AFURLSessionManager 内部维护了一个可变字典属性 mutableTaskDelegatesKeyedByTaskIdentifier,以任务对象的标识符 taskIdentifier 作为键,以 AFURLSessionManagerTaskDelegate 对象作为值。在初始化任务对象时,就绑定一个 AFURLSessionManagerTaskDelegate 对象,以处理进度、下载数据。具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 根据特定 request 创建一个 NSURLSessionDataTask
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler
{
// 创建任务对象
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});

// 为任务对象设置代理对象、上传进度回调、下载进度回调、完成回调
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

return dataTask;
}

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 创建一个 AFURLSessionManagerTaskDelegate 对象,用以处理进度、下载数据
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;

// 用来标识任务对象所属的 SessionManager
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];

delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);

// 保存 NSURLSessionTask <-> AFURLSessionManagerTaskDelegate 的映射关系
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}

AFURLSessionManagerTaskDelegate 主要实现了以下这些协议方法。在这些方法内实现进度管理、下载管理。AFURLSessionManager 虽然也实现了这些协议,但是最终还是会调用 AFURLSessionManagerTaskDelegate 中的实现。

  • NSURLSessionTaskDelegate
    • URLSession:task:didCompleteWithError:
    • URLSession:task:didFinishCollectingMetrics:
  • NSURLSessionDataDelegate
    • URLSession:dataTask:didReceiveData:
    • URLSession:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

通知转发

NSURLSession 定义了一系列通知,为了提供更完美的封装,AFURLSessionManager 捕获了这些通知,将它们转换成自己所定义的通知后,再进行转发。

这里,就用到了任务对象的 taskDescription 属性。taskDescription 属性描述了该任务对象所属的 Session Manager。因为应用程序中可能会初始化多个 AFURLSessionManager。Session Manager 只有在捕获到它所管理的任务对象发出的通知后才会进行封装和转发。如:

  • AFNetworkingTaskDidResumeNotification
  • AFNetworkingTaskDidSuspendNotification

AFHTTPSessionManager

接下来,我们再来介绍一下 AFURLSessionManager 的子类——AFHTTPSessionManager

首先,我们来看一下 AFHTTPSessionManager 所特有的属性。

1
2
3
4
5
6
7
8
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>

@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

@end

由于,AFHTTPSessionManager 主要用于 HTTP 请求,所以它为 HTTP 请求提供了一个请求序列化器和一个响应序列化器,并分别设置默认值为 HTTP 和 JSON。

除此之外,AFHTTPSessionManager 还封装了 AFURLSessionManager 的一些复杂的任务对象初始化方法,并提供了一些非常简单的便利方法,用以提供各种 HTTP 请求。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

结论

总而言之,AFNetworking 实际上只是对 NSURLSession 高度地封装, 提供一些简单易用的 API 方便我们在 iOS 开发中发出网络请求并在其上更快地构建网络层组件并提供合理的接口。

后面,有机会我们继续深入了解一下 AFNetworking 的其他辅助类以及分类功能。

参考

  1. AFNetworking
  2. AFNetworking 概述(一)

(完)