关于AFNetworking的简单总结。


终于有时间把AFNetworking源码看完了,做个记录。

一、AFHTTPSessionManager

1,基本使用

引入AFNetworking框架后,我们使用AFHTTPSessionManager可以完成网络请求相关的工作。

AFHTTPSessionManager的头文件如下:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
// 设置manager对应的baseUrl, 用于构造出相对路径的URL
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
// 请求序列化器, 用于设定编码类型、缓存策略、超时等配置
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
// 响应序列化器, 主要用于设置可接收编码类型等属性, 默认是AFJSONResponseSerializer
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
// 安全策略, 默认是[AFSecurityPolicy defaultPolicy]
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
// 不是单例, 会"创建"一个AFHTTPSessionManager实例
+ (instancetype)manager;
// 根据指定的baseUrl初始化一个AFHTTPSessionManager实例
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
// 根据指定的baseUrl和NSURLSessionConfiguration 初始化一个AFHTTPSessionManager实例
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
// 发起GET请求,downloadProgress是在operationQueue中调用,而非主线程
- (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;
// 发起HEAD请求
- (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;
// 发起POST请求,progress是在operationQueue中调用,而非主线程
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
// 发起POST请求,progress是在operationQueue中调用,而非主线程。constructingBodyWithBlock要求传入一个遵守AFMultipartFormData协议的对象
- (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;
// 发起PUT请求
- (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;
// 发起PATCH请求,PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新
- (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;
// 发起DELETE请求
- (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;
// 自定义HTTPMethod创建一个NSURLSessionDataTask。uploadProgress和downloadProgress都是在operationQueue中调用,而非主线程
- (nullable NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
@end

如上所述,忽略requestSerializer、responseSerializer、securityPolicy等属性,对于外界来说,只需要关注发起不同请求方式的接口:GET、HEAD、POST、PUT、DELETE等。

通常使用方式是创建一个AFHTTPSessionManager的子类,并设置为单例。做一些简单的配置,比如设置通用的Base URL、安全策略等:

1
2
3
4
5
6
7
8
9
10
11
12
static NSString * const AFAppDotNetAPIBaseURLString = @"https://api.app.net/";

@implementation AFAppDotNetAPIClient
+ (instancetype)sharedClient {
static AFAppDotNetAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]];
});
return _sharedClient;
}
@end

比如GET请求,就可以这样使用:

1
2
3
4
5
[[AFAppDotNetAPIClient sharedClient] GET:@"stream/0/posts/stream/global" parameters:nil headers: nil progress:nil success:^(NSURLSessionDataTask * __unused task, id JSON) {
// 成功回调
} failure:^(NSURLSessionDataTask *__unused task, NSError *error) {
// 失败回调
}];

使用POST请求上传文件则是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[[AFAppDotNetAPIClient sharedClient] POST:@"stream/0/posts/upload" parameters:nil headers:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
// 获取文件数据
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"userfile.jpg" ofType:nil];
if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) {
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
if (fileData) {
// 拼接数据
[formData appendPartWithFileData:fileData name:@"userfile" fileName:[filePath lastPathComponent] mimeType:@"image/jpg"];
}
}
} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// 成功回调
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// 失败回调
}];

上传文件的时候,我们只需要关注怎么使用这个”formData“即可,而不用也不必关心它是什么、有哪些属性,这就是协议的妙处。

2,AFHTTPSessionManager的实现

AFHTTPSessionManager继承自AFURLSessionManager,用于支持HTTP(S)协议的网络请求,对外提供常见的HTTPMethod的接口,并返回NSURLSessionDataTask实例。比如GET请求方式是这么实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure {
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
headers:headers
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}

其他请求方式也类似,均指向了统一的入口方法,这个入口方法根据不同的HTTPMethod生成相应的NSURLSessionDataTask实例,这个入口方法做了这些事:
(1)借助AFURLRequestSerialization生成NSURLRequest。实际上AFURLRequestSerialization是协议,默认是使用实现这个协议的AFHTTPRequestSerializer类生成NSURLRequest。
(2)把外界的headers信息复制到NSURLRequest的请求头中。
(3)调用父类(AFURLSessionManager)的dataTaskWithRequest方法,传入request、uploadProgress、downloadProgress等参数,生成NSURLSessionDataTask并返回。

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
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure {
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
// 设置请求头
for (NSString *headerField in headers.keyEnumerator) {
[request setValue:headers[headerField] forHTTPHeaderField:headerField];
}
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
// 创建DataTask
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) failure(dataTask, error);
} else {
if (success) success(dataTask, responseObject);
}
}];
return dataTask;
}

可见,AFHTTPSessionManager作为对外的接口,内部实现也非常简洁,复杂的逻辑都交给了上层实现。最关键的有两部分,一是通过AFURLRequestSerialization生成NSURLRequest,二是通过父类(AFURLSessionManager)处理NSURLRequest并生成NSURLSessionDataTask。

3,父类AFURLSessionManager

AFURLSessionManager本质上高度封装了NSURLSession的逻辑,从接口层面可以看到:
(1)管理NSURLSession,遵守其协议,作为代理实现它的相关回调。
(2)外界可以配置请求序列化实例(AFURLRequestSerialization)和响应序列化实例(AFURLResponseSerialization),进而处理Request和Response。
(3)提供普通任务、上传任务、下载任务的构建方法并统一管理所有的任务,通过Block形式对外提供各种任务的各阶段的回调。
(4)利用NSProgress对外提供上传任务和下载任务的进度。
(5)网络请求安全策略验证。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
// _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
@property (readonly, nonatomic, strong) NSURLSession *session;
// delegate相关的回调会在这个queue中进行, 比如上传、下载的回调
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
// 序列化对象, 默认是AFJSONResponseSerializer
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
// 安全相关的策略. 默认是[AFSecurityPolicy defaultPolicy]
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
#if !TARGET_OS_WATCH
// 网络状态监听器, 默认是[AFNetworkReachabilityManager sharedManager]
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
#endif

// 所有的data upload download任务集合
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
// 所有的data任务集合
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
// 所有的upload任务集合
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
// 所有的download任务集合
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;

// completionBlock回调的队列, 默认在主线程回调, 可以自行设置
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
// 所有completionBlock回调的group, 默认使用AFN自己创建的(url_session_manager_completion_group())。在外部发起多个请求, 然后通过completionGroup, 在请求完成后统一执行
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
// 通过NSURLSessionConfiguration初始化一个manager, 默认使用[NSURLSessionConfiguration defaultSessionConfiguration]
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
// 把manager的session取消(invalidateAndCancel)掉
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks resetSession:(BOOL)resetSession;
// 创建DataTask, uploadProgressBlock和downloadProgressBlock这些回调是在operationQueue中调用.
- (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;
// 创建UploadTask, 三种创建方式, NSURLRequest+NSURL, NSURLRequest+NSData, 还可以仅使用NSURLRequest. uploadProgressBlock回调是在operationQueue中调用.
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(nullable NSData *)bodyData
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
// 创建DownloadTask, 三种创建方式, NSURLRequest+NSURL, NSURLRequest+NSData, 还可以仅使用NSURLRequest. uploadProgressBlock是在operationQueue中调用.
// 通过NSURLRequest或者resumeData创建DownloadTask, downloadProgressBlock是在operationQueue中调用. 下载的临时文件在下载完毕后自动删除, 如果使用手动创建的NSURLSessionConfiguration, 并且它的类型是Background, 下载完成回调会在App终止时丢失. 后台下载任务最好使用`-setDownloadTaskDidFinishDownloadingBlock:` 来设置文件的完成回调, 而不能依赖仅仅destination回调.
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
// 获取上传Task和下载Task的NSProgress
- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;
- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;


// 设置Session的Invalid回调
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
// 当一个任务强制要求发送一个新的请求体流到远程服务器时被调用 (证书验证或者其他recoverable server error时会用到)
- (void)setTaskNeedNewBodyStreamBlock:(nullable NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block;
// 当一个HTTP请求尝试执行一个重定向时被调用
- (void)setTaskWillPerformHTTPRedirectionBlock:(nullable NSURLRequest * _Nullable (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block;
// 用于跟踪上传进度, block会在主线程被调用多次
- (void)setTaskDidSendBodyDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block;
// task完成时调用
- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block;
// 性能指标收集完毕时调用
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
- (void)setTaskDidFinishCollectingMetricsBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * _Nullable metrics))block AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
#endif
// data task接收到response时回调
- (void)setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block;
// data task开始下载时回调
- (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
// data task接收到数据时调用, 这个block会在operationQueue中被调用很多次
- (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
// 用于确认是否需要缓存response
- (void)setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
// session的message全部被分发出去后调用
- (void)setDidFinishEventsForBackgroundURLSessionBlock:(nullable void (^)(NSURLSession *session))block AF_API_UNAVAILABLE(macos);
// 下载任务完成回调
- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block;
// 用于跟踪下载进度
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
// 下载任务恢复
- (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block;
// 如果想完全绕过AFN的AFSecurityPolicy, 可以自己在这里设置一个刚接收到Challenge时的回调. 如果不想绕过AFN的安全策略, 可以使用 `-setAuthenticationChallengeHandler:`
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
// 设置证书Challenge回调
- (void)setAuthenticationChallengeHandler:(id (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition , NSURLCredential * _Nullable)))authenticationChallengeHandler;
@end

AFURLSessionManager暴露出很多接口,很大一部分是NSURLSession的代理以Block形式透传,外界可以更加优雅这些回调。

4,AFURLSessionManager的实现

NSURLSession是网络请求的核心,AFURLSessionManager强引用NSURLSession并作为它的delegate,而Session因为其场景的特殊性,它的delegate是retain修饰的强引用:

1
@property (nullable, readonly, retain) id <NSURLSessionDelegate> delegate;

二者循环引用,这也就是为什么最开始推荐使用单例方式使用AFURLSessionManager的原因,否则随意的创建AFURLSessionManager,而不及时打破循环引用,会造成内存泄漏。

类似delegate是强引用的例子在Cocoa框架中还有许多,比如CAAnimation(@property(nullable, strong) id <CAAnimationDelegate> delegate;),因为动画和网络请求是在异步执行,而异步执行中delegate随时可能会被销毁,所以需要设置delegate为强引用来保证在网络请求、动画的整个过程中都有着陆点、响应都能正确进行。

在构造方法中可以看到,除了一些基本属性的配置,AFURLSessionManager维护了一份可变字典mutableTaskDelegatesKeyedByTaskIdentifier,把任务与任务的代理建立映射,分门别类地把普通任务、下载任务、上传任务及其代理统一管理。

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
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
// ......

// 默认值是NSOperationQueueDefaultMaxConcurrentOperationCount,这里设置最大并发数量为1, 表示串行队列
// 为了保证callback的调用顺序, Session要求传入串行队列
// The queue should be a serial queue, in order to ensure the correct ordering of callbacks
self.operationQueue.maxConcurrentOperationCount = 1;
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

// 获取当前session中的所有有效的task, 并逐一设置delegate
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
// @property (nullable, retain) id <NSURLSessionTaskDelegate> delegate
// task的delegate也是retain
// manager强引用session session强引用manager
// manager强引用session, session管理所有有效的task
// manager通过mutableTaskDelegatesKeyedByTaskIdentifier强引用taskDelegate, taskDelegate弱引用manager, taskDelegate不持有task, task强引用taskDelegate
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}

为了逻辑清晰,作者创建了AFURLSessionManagerTaskDelegate用来专门处理Task的代理逻辑。但是为了接口统一,统筹处理回调接口,AFURLSessionManager既作为NSURLSession的代理,也作为所有任务的代理。也即,AFURLSessionManager起到桥梁的作用。
纵观全部,AFURLSessionManager主要做了两件核心的事:
(1)管理所有Task及TaskDelegate。通过一份Map,将Task与TaskDelegate建立映射,自身虽然是所有Task的代理,但是却把具体实现转交给TaskDelegate处理。
(2)协调NSURLSession,自身作为NSURLSession的代理,实现协议中的方法并以Block回调的方式作为接口透出。

管理所有Task及TaskDelegate

AFURLSessionManager对外提供了四个接口:获取普通任务、获取上传任务、获取下载任务、获取全部任务,但是实现上怎么获取NSURLSession的任务呢,可以发现,NSURLSession提供了getTasksWithCompletionHandler方法获取它的各种Task。

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
- (NSArray *)tasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)dataTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)uploadTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)downloadTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
// 使用信号量 异步转同步
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
// KVC语法糖, 数组合并
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}

_cmd就是方法的Selector,作者偷了个懒。最终是通过tasksForKeyPath方法来获取相应种类的任务数组。这个方法用到了信号量,起到把异步方法getTasksWithCompletionHandler转同步的作用。KVC的中@"@unionOfArrays.self"语法糖,实则相当于:

1
2
3
4
NSMutableArray *array = [NSMutableArray arrayWithArray:dataTasks];
[array addObjectsFromArray:uploadTasks];
[array addObjectsFromArray:downloadTasks];
tasks = array.copy;

对于管理的每一个任务,我们比较关心的是它的状态,当任务状态改变的时候需要发出全局的通知,供外界消费,目前有6种状态的通知:

Session失效:AFURLSessionDidInvalidateNotification
任务完成:AFNetworkingTaskDidCompleteNotification
任务恢复:AFNetworkingTaskDidResumeNotification
任务挂起:AFNetworkingTaskDidSuspendNotification
下载任务临时文件移动失败:AFURLSessionDownloadTaskDidFailToMoveFileNotification
下载任务临时文件移动成功:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification

很轻松的,因为AFURLSessionManager是NSURLSession和所有任务的代理,可以在相关代理方法中发出这个任务的状态通知,比如Session失效:

1
2
3
4
5
6
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

实际上AFURLSessionManager自己只发出了AFURLSessionDidInvalidateNotification、AFNetworkingTaskDidResumeNotification和AFNetworkingTaskDidSuspendNotification这三种通知,另外三种是交给AFURLSessionManagerTaskDelegate发出的。

任务已经清晰明了了,那如何管理所有的任务的代理呢?首先获取每个任务的唯一标识符taskIdentifier,再建立一个可变字典,把taskIdentifier与任务的代理一一对应。在多线程环境中,管理这些任务的代理自然要考虑线程安全问题。而NSMutableDictionary并不是线程安全的,所以在读写字典的时候需要加一个锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task {
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}

- (void)removeDelegateForTask:(NSURLSessionTask *)task {
[self.lock lock];
[self removeNotificationObserverForTask:task];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}

到此为止,AFURLSessionManager可以控制所有种类的任务、控制任务对应的代理以及处理所有任务的状态了。

协调NSURLSession

AFURLSessionManager核心逻辑都是围绕着NSURLSession处理的,网络请求的功能也是依赖它实现的。一个AFURLSessionManager只需要一个NSURLSession实例,所以在创建NSURLSession的时候作者使用了@synchronized锁,避免重复创建。

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实现了所有必要的代理方法,并在代理方法中调用AFURLSessionManagerTaskDelegate的“同名”方法,同时处理外界的接口回调。以taskDidComplete属性为例,首先根据Task获取对应的AFURLSessionManagerTaskDelegate,然后调用AFURLSessionManagerTaskDelegate的同名回调,最后如果外界有设置回调则调用。对于Session的代理处理,几乎都是这三个逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// 当task在后台完成, delegate可能为空
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}

if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}

但是有一个代理方法的实现需要着重看一下,那就是对质询的处理。先回顾下HTTPS的主要流程:

https://juejin.cn/post/6844903906560966670
验证流程:
1,客户端生成随机数Client random, 声明支持的加密方式, 发送给服务端. (ClientHello)
2,服务端确认加密方式, 生成随机数Server random, 给出服务端证书, 发送给客户端. (SeverHello, SeverHello Done)
3,如果服务端要求双向认证,则客户端需要提供客户端证书给服务端(Client Key Exchange); 接着客户端验证服务端证书是否合法, 生成随机数Pre-Master, 并使用服务端证书中的公钥进行加密, 发送给服务端. (Certificate Verify)
4,服务端使用自己的证书私钥, 解密客户端发送来的加密信心, 得到Per-Master
5,客户端和服务端此时拥有三个相同的随机数, 按照相同算法生成对话私钥, 彼此互相使用对话私钥加密Finish信息互相确认私钥正确性, 握手完成.

中间人攻击的类型
方式一:用CA申请的证书
验证证书的信息摘要是否被修改。处理权威机构签发的证书对于权威机构签发的证书, 这类证书上面会声明自己是由哪一个CA机构(或CA的子机构)签发, 而对应的CA机构也有自己的CA证书, 在手机出厂之前就被安装进系统里了,这样对于权威机构签发的服务器证书,只要从系统里找一下服务器证书对应的CA证书, 拿CA证书的公钥解密一下服务器证书的签名, 解密出的Hash是不是和服务器携带的数据部分运算出的Hash一致, 即可证明服务器证书是合法的。

比如A和B通信,B的公钥和数字证书被O截取。而O原先已将自己的公钥上传CA,并得到了CA签发的一张等效的数字证书。于是O将B的公钥和证书一起替换成O的公钥和O的证书,再发给A。A能够识别自己正在遭受中间人攻击吗?
服务方向第三方机构CA提交公钥、组织信息、个人信息(域名)等信息并申请认证,CA通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等。证书 = 公钥(服务方生成密码对中的公钥)+申请者与颁发者信息+签名(用CA机构生成的密码对的私钥进行签名。

方式二:用自签名的证书
验证证书的签发机构是否受信任。

这个方法的使用场景有两个:
(1)When a remote server asks for client certificates or Windows NT LAN Manager (NTLM) authentication, to allow your app to provide appropriate credentials
(2)When a session first establishes a connection to a remote server that uses SSL or TLS, to allow your app to verify the server’s certificate chain
If you do not implement this method, the session calls its delegate’s URLSession:task:didReceiveChallenge:completionHandler: method instead.
也即双向验证时提供客户端证书和验证服务端证书。主要的应用场景是验证服务端证书,服务端证书分为两种情况:权威机构颁发的证书和自制的证书。

如何处理证书:

1
2
3
4
NSURLSessionAuthChallengeUseCredential = 0, 使用该证书 安装该证书
NSURLSessionAuthChallengePerformDefaultHandling = 1, 默认采用的方式, 该证书被忽略
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 取消请求, 证书忽略
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒绝

质询验证方式:

1
2
3
NSURLAuthenticationMethodHTTPBasic // HTTP基本验证,服务器向客户端询问用户名,密码
NSURLAuthenticationMethodClientCertificate// 客户端证书验证,服务器向客户端询客户端身份证书
NSURLAuthenticationMethodServerTrust// 服务器端证书验证,客户端对服务器端的证书进行验证。HTTPS中的服务器端证书验证属于这一种。

CA颁发的证书验证逻辑是校验证书的合法性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 判断服务器的证书是否合法
SecTrustResultType result;
SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);
if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {
NSLog(@"合法");
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
} else {
NSLog(@"不合法");
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
// 这里只处理单向认证, 其他情况不考虑
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}

自签名的证书验证逻辑是校验证书的合法性和一致性:

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
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
// 服务器的证书
NSURLCredential *serverCredential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 本地自签名证书
NSData *certificateData = [NSData dataWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"certificate" ofType:@"der"]];
// 这里需要用数组存放证书
NSMutableArray *pinnedCertificates = [NSMutableArray array];
// 加入本地证书
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
// 设置本地证书为受信任的”锚点证书“
SecTrustSetAnchorCertificates(challenge.protectionSpace.serverTrust, (__bridge CFArrayRef)pinnedCertificates);
SecTrustResultType result;
// 验证服务器证书是否可信
SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);
if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {
NSLog(@"合法");
// 获取服务端证书所包含的证书链
CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}
NSArray *serverCertificates = [NSArray arrayWithArray:trustChain];
// 检查服务器证书是否包含本地证书
if ([serverCertificates containsObject:certificateData]) {
NSLog(@"服务器证书和本地证书相同");
completionHandler(NSURLSessionAuthChallengeUseCredential, serverCredential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
NSLog(@"不合法");
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}

再看一下AFN是怎么处理的:

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
49
50
51
52
53
54
55
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
BOOL evaluateServerTrust = NO;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
if (self.authenticationChallengeHandler) {
// 获取处理结果
id result = self.authenticationChallengeHandler(session, task, challenge, completionHandler);
if (result == nil) {
return;
} else if ([result isKindOfClass:NSError.class]) {
// 设置error, 在didCompleteWithError透传
objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN);
// 取消请求, 证书忽略
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
} else if ([result isKindOfClass:NSURLCredential.class]) {
credential = result;
// 使用该证书 安装该证书
disposition = NSURLSessionAuthChallengeUseCredential;
} else if ([result isKindOfClass:NSNumber.class]) {
disposition = [result integerValue];
NSAssert(disposition == NSURLSessionAuthChallengePerformDefaultHandling || disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge || disposition == NSURLSessionAuthChallengeRejectProtectionSpace, @"");
// NSURLAuthenticationMethodServerTrust: 质询的验证方式是验证服务器证书
evaluateServerTrust = disposition == NSURLSessionAuthChallengePerformDefaultHandling && [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
} else {
// 抛出异常 无效的返回值
@throw [NSException exceptionWithName:@"Invalid Return Value" reason:@"The return value from the authentication challenge handler must be nil, an NSError, an NSURLCredential or an NSNumber." userInfo:nil];
}
} else {
// 判断证书的类型是否为服务器信任
evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

if (evaluateServerTrust) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 执行安全策略成功 安装该证书
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
// 设置error, 在didCompleteWithError透传
// serverTrustErrorForServerTrust:构造证书不信任的error实例
objc_setAssociatedObject(task, AuthenticationChallengeErrorKey,
[self serverTrustErrorForServerTrust:challenge.protectionSpace.serverTrust url:task.currentRequest.URL],
OBJC_ASSOCIATION_RETAIN);
// 取消请求, 证书忽略
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}

if (completionHandler) {
completionHandler(disposition, credential);
}
}

除此之外,有一块代码让人感到非常严谨。AFURLSessionManager重写了respondsToSelector方法,一些selector是否实现的判断逻辑映射到相应的外界接口,也即AFURLSessionManager是否响应某些方法取决于外界是否设置了相应的回调,可谓巧妙又严谨。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (BOOL)respondsToSelector:(SEL)selector {
if (selector == @selector(URLSession:didReceiveChallenge:completionHandler:)) {
return self.sessionDidReceiveAuthenticationChallenge != nil;
} else if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
return self.taskWillPerformHTTPRedirection != nil;
} else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
return self.dataTaskDidReceiveResponse != nil;
} else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
return self.dataTaskWillCacheResponse != nil;
}
#if !TARGET_OS_OSX
else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
return self.didFinishEventsForBackgroundURLSession != nil;
}
#endif
return [[self class] instancesRespondToSelector:selector];
}

TaskDelegate的引入

在创建任务的时候,同时创建与其对应的AFURLSessionManagerTaskDelegate,由这个Delegate处理任务的代理的逻辑,做到了职责分离,分工明确。addDelegateForDataTask方法把Task与TaskDelegate建立关联,其实是基本属性的配置。

1
2
3
4
5
6
7
8
9
10
11
12
- (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 *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}

在AFURLSessionManagerTaskDelegate的接口文件中可以看到,它遵循了NSURLSessionTaskDelegateNSURLSessionDataDelegateNSURLSessionDownloadDelegate三种协议,不引用Task,弱引用AFURLSessionManager。构造方法initWithTask要求传入Task但是却不持有,主要是因为这个Task是给NSProgress用的,自身全程不需要用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
- (instancetype)initWithTask:(NSURLSessionTask *)task;
// 弱引用manager
@property (nonatomic, weak) AFURLSessionManager *manager;
@property (nonatomic, strong) NSMutableData *mutableData;
@property (nonatomic, strong) NSProgress *uploadProgress;
@property (nonatomic, strong) NSProgress *downloadProgress;
@property (nonatomic, copy) NSURL *downloadFileURL;
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
#endif
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
@end

TaskDelegate的逻辑

AFURLSessionManagerTaskDelegate有三个核心逻辑,一个是处理上传任务和下载任务的进度,另一个是下载任务的收尾,还有一个是所有任务完成后的收尾。

处理任务的进度

作者利用NSProgress来表征任务的进度信息(上传进度、下载进度)。从TaskDelegate的构造方法中可以看到,创建一个上传任务的NSProgress和一个下载任务的NSProgress,在NSProgress的取消回调、暂停回调、恢复回调中设置对应的改变任务状态的方法。利用KVO监听任务的fractionCompleted属性(某个任务已完成量占总量的比例),把任务进度的实时改变传递出去。

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
49
50
51
52
53
- (instancetype)initWithTask:(NSURLSessionTask *)task {
self = [super init];
if (!self) {
return nil;
}
_mutableData = [NSMutableData data];
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
// 配置_uploadProgress和_downloadProgress 主要是取消、暂停、挂起三个回调与task保持同步
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
#if AF_CAN_USE_AT_AVAILABLE
if (@available(macOS 10.11, *))
#else
if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
{
progress.resumingHandler = ^{
[weakTask resume];
};
}
// 使用KVO监听两个progress的进度(fractionCompleted) 某个任务已完成量占总量的比例
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}

// 借助NSProgress给外界 任务上传和任务下载的进度回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}

一切布置妥当,只需要适时更改NSProgress的totalUnitCount和completedUnitCount即可,比如:

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
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
// 上传task的进度配置
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}

// 每当写入数据到临时文件时, 就会调用一次这个方法, 更新下载的进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{

self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}

// 下载任务恢复时, 更新下载的进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{

self.downloadProgress.totalUnitCount = expectedTotalBytes;
self.downloadProgress.completedUnitCount = fileOffset;
}

这样就做到了完美的进度透出工作。

下载任务的收尾

下载任务的资源下载完毕,先把临时文件移动到目标下载路径,之后发出AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 下载任务完成
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
// 任务完成, 清除downloadFileURL
self.downloadFileURL = nil;
// 外界设置了回调才调用
if (self.downloadTaskDidFinishDownloading) {
// 获取到外界配置的downloadFileURL
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
// 把临时文件移动到downloadFileURL
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
}
}
}
}

所有任务完成后的收尾

所有任务结束的时候需要做收尾工作,主要逻辑是:
(1)配置AFNetworkingTaskDidCompleteNotification通知的userInfo内容,包括性能指标、资源路径、responseObject、错误信息等,并在主线程发出这个通知。
(2)使用dispatch_group,把所有任务的completionHandler放到group中执行。外界可拿到这个group,从而利用dispatch_group_notify获取所有回调完成的时机。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 这是所有task完成后调用的父类方法, 比如下载任务完成后会在自己的delegate里面做文件的移动操作, 但是也需要在这个方法里做最终的收尾
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
// 优先使用自己给task设置过的error, 没有再用回调中的
// 在处理didReceiveChallenge时可能会提前给task设置一个error
error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;
// self.manager是弱引用, 在这里避免被提前释放,使用强引用
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
// 内存回收
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
self.mutableData = nil;
}
#if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
if (self.sessionTaskMetrics) {
userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
}
}
#endif
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
// 主线程发出通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
// 序列化
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
// 下载任务, 使用目标地址作为responseObject
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}

if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
// 序列化error
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
// 主线程发出通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}

二、AFHTTPRequestSerializer

AFURLRequestSerialization是一种协议,只包含一个方法,定义了根据NSURLRequest和参数(parameters)生成新的NSURLRequest的规则。

1
2
3
4
5
6
7
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
// 把拼接的参数塞到request里面
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

AFHTTPRequestSerializer遵循了这个AFURLRequestSerialization协议,实际上AFHTTPRequestSerializer也是主要做了这件事:整合请求的请求头、参数、配置等信息,输出一个NSURLRequest。

1,基本使用

AFHTTPRequestSerializer定义了请求的参数、请求头,还有配置,比如缓存策略、超时时间、请求头、编码方式、HTTP管线化等,并且可以通过它创建定制化的NSURLRequest。AFHTTPRequestSerializer还向下派生出两个子类:用于支持内容类型为JSON(Content-Type为application/json)的AFJSONRequestSerializer,以及用于支持内容类型为属性列表文件(Content-Type为application/x-plist)的AFPropertyListRequestSerializer。

HTTP管线化
通常默认情况下请求和响应是顺序的,也就是说请求–>得到响应后,再请求。
如果将HTTPShouldUsePipelining设置为YES,则允许不必等到response,就可以再次请求。这个会很大的提高网络请求的效率。
因为客户端无法正确的匹配请求与响应,所以这依赖于’服务器必须保证’,响应的顺序与客户端请求的顺序一致。如果服务器不能保证这一点,那可能导致响应和请求混乱。

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
49
50
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
// 参数的编码形式, 默认是NSUTF8StringEncoding
@property (nonatomic, assign) NSStringEncoding stringEncoding;
// 创建请求的时候是否可以使用移动网络, 默认YES
@property (nonatomic, assign) BOOL allowsCellularAccess;
// request的缓存策略, 默认是NSURLRequestUseProtocolCachePolicy
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
// request是否应该使用默认的cookie处理方式, 默认是YES
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
// 是否可以在未收到response的时候就发出下一个request, 默认是NO
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
// request的网络服务类型, 默认是NSURLNetworkServiceTypeDefault, 比如NSURLNetworkServiceTypeVoice、NSURLNetworkServiceTypeVideo等
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
// 请求的超时时长 单位秒 默认60秒
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
// HTTP的请求头, 默认携带的有:Accept-Language(NSLocale.preferredLanguages)和User-Agent。 添加或者移除用 setValue:forHTTPHeaderField: 这个方法
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
// 根据默认的配置“创建“一个序列化对象
+ (instancetype)serializer;
// 设置请求头字段, 如果value为nil, 则表示删除
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(NSString *)field;
// 获取指定请求头字段
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
// 设置验证时需要的用户名密码, 要求Base64编码
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password;
// 移除Authorization请求头
- (void)clearAuthorizationHeader;
// 对哪些请求方式需要做query的编码, 默认是`GET`, `HEAD` 和 `DELETE` https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
// 设置请求参数的编码类型, 目前只有一种 AFHTTPRequestQueryStringDefaultStyle
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
// 自定义请求参数的编码操作
- (void)setQueryStringSerializationWithBlock:(nullable NSString * _Nullable (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
// 创建一个NSMutableURLRequest. 如果请求方法是`GET`, `HEAD` 或者 `DELETE`, parameters会被编码后拼接到URL的后面, 否则会被编码后设置为parameterEncoding属性, 存放到请求体中
- (nullable NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
// 通过构造`multipart/form-data`请求体创建一个NSMutableURLRequest, 最终把data存放在request的HTTPBodyStream属性或者HTTPBody属性中.
// 请求方法不能为GET`和 `HEAD`, parameters会被设定到请求体中,
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
// 将原来request中的HTTPBodyStream内容异步写入到指定文件中,随后调用completionHandler处理。最后返回新的request。
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
@end

AFHTTPRequestSerializer对外输出三种类型的NSURLRequest,分别是普通类型、上传数据类型和下载数据类型,为了支持表单上传的Request,作者还定义了一种协议:AFMultipartFormData。使用这个协议中定义的方法用来做数据上传工作,比如通过传入文件的路径、传入NSInputStream、或者文件的NSData等上传数据。实际上是AFStreamingMultipartFormData实现了这个协议中的方法,做了这些逻辑,但是对于外界来说,这个类的隐藏的,外界只看到了接口,只需要调用接口即可。

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
@protocol AFMultipartFormData
// 把`Content-Disposition: file; filename=#{generated filename}; name=#{name}"` 和 `Content-Type: #{generated mimeType}` 设置到请求头中
// 文件名和MIME类型会自动生成, 生成规则: 文件名使用fileURL的最后一个路径, MIME类型使用fileURL的扩展名。这里的fileURL不能为空
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
// 把`Content-Disposition: file; filename=#{filename}; name=#{name}"` 和 `Content-Type: #{mimeType}`设置到请求头中。这里的fileURL、name、fileName和mimeType均不能为空
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
// 通过NSInputStream上传文件
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
// 通过文件的Data上传 比如: [formData appendPartWithFileData:vedioData name:@"video1" fileName:@"video1.mov" mimeType:@"video/quicktime"];
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
// 通过Data上传 比如: [formData appendPartWithFormData:data name:@"appInfo"];
- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name;
// 拼接请求头 比如: [self appendPartWithHeaders:@{@"Content-Type": @"image/jpeg"} body:data];
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers body:(NSData *)body;
// 通过限制数据包大小和为从上传流中读取的每个块添加延迟来限制请求带宽 numberOfBytes:最大数据包大小, 单位字节, 默认是16kb; delay: 延迟时间: 默认没有设置
// 这两个属性直接设置到inputStream中
// iOS编程中throttle那些事 http://www.cocoachina.com/articles/18525
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay;
@end

2,AFHTTPRequestSerializer的实现

通过上面的接口可以发现,其实AFHTTPRequestSerializer做的就是发起请求前的准备工作,最终输出的NSURLRequest是它做的工作的结果,但是它并没有把NSURLRequest暴露出来。换句话说,这个NSURLRequest是一个方法的返回值,但是却没有作为AFHTTPRequestSerializer的属性。这样,就牵扯到属性映射问题。

NSURLRequest的六个属性

AFHTTPRequestSerializer有六个属性比较特殊,分别是allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType和timeoutInterval。实际上这六个属性会映射到NSURLRequest中的同名属性,这下很清晰了,设置AFHTTPRequestSerializer的这些属性,就是设置NSURLRequest的同等属性。但是当设置这六个属性的值的时候,AFHTTPRequestSerializer本身没有NSURLRequest这个成员变量,没法把这些属性值直接设置到NSURLRequest上面,所以才有这一大块逻辑。看看作者如何处理:

在AFHTTPRequestSerializer初始化的时候对这六个属性添加KVO监听,并维护一个动态更新的列表mutableObservedChangedKeyPaths(NSSet类型)。当外界对某个属性设值的时候,把这个属性存储到列表中,当外界对某个属性设空值(nil)的时候,把这个属性从列表中移除。

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
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
....

self.mutableObservedChangedKeyPaths = [NSMutableSet set];
// KVO监听 allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、 networkServiceType、timeoutInterval 这些属性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { // 获取这六个属性对应的字符串数组
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
// 指定了上下文 AFHTTPRequestSerializerObserverContext
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == AFHTTPRequestSerializerObserverContext) {
// 新值不存在, 表示把这个字段从请求头中剔除
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}

这样,mutableObservedChangedKeyPaths永远是最新的外界设置过属性的列表。最后在创建Request的时候,将这些设置信息同步到NSURLRequest中:

1
2
3
for (NSString *keyPath in self.mutableObservedChangedKeyPaths) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}

这里就有疑问了,为啥要多此一举弄一个KVO,外界有设置过属性,AFHTTPRequestSerializer本身就会存储,在创建Request的时候直接拿出来同步不就好了,多个KVO的好处是啥?

请求头的多读单写

可变字典mutableHTTPRequestHeaders是内部请求头的信息集合,在多线程环境下,需要做到多读单写,保证线程安全。这里用并发队列+栅栏函数实现。

1,valueForHTTPHeaderField:HTTPRequestHeaders要求有返回值,所以使用同步函数。
2,requestHeaderModificationQueue是自己创建的并发队列,但是读写操作都是使用同步函数,所以这些操作都是在当前线程。
2,setValue:forHTTPHeaderField:clearAuthorizationHeader使用了栅栏函数,实现设值操作和其他操作(取值操作)互斥,其他操作结束后才能有设值操作,设值操作不能并发执行,从而保证线程安全。

再回顾一下栅栏函数:

When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the queue executes the barrier block by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

所以可以总结为:
(1)当使用栅栏函数向队列添加任务时,必须等待队列中已有的任务执行完毕,栅栏函数中的任务才会执行。
(2)必须等待栅栏函数中的任务执行完毕,其他任务才会执行。

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
- (NSDictionary *)HTTPRequestHeaders {
NSDictionary __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
});
return value;
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}

- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
// 这里是同步写操作, 异步也可以
dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}

- (void)clearAuthorizationHeader {
dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
});
}

上面的读写操作可以总结为下面三条:
(1)可以同时有多个读操作,读操作可以并发执行。
(2)不能同时有多个写操作,写操作串行执行,在写操作之前,读操作都完成。
(3)写操作与读操作、其他写操作是互斥的(读操作的时候,不能有写操作,写操作的时候,不能有读操作)。

回到逻辑上,mutableHTTPRequestHeaders存储着请求头的信息,在创建NSURLRequest的时候,把这个信息同步到NSURLRequest上即可。

1
2
3
4
5
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];

NSURLRequest构建

对于一般的请求,主要有四块逻辑:
(1)NSURLRequest的参数配置(就是上文所说的mutableObservedChangedKeyPaths的使用)。
(2)请求头的处理,把请求头字段和值逐一设置到Request中。
(3)请求参数的序列化,当外界有特定的序列化方式时,调用外界的接口生成字符串,否则采用默认的方式,即通过”&“拼接成字符串。
(4)请求参数的位置,默认情况下对于GET, HEADDELETE这三种请求方式,参数串是放在URL中,其他的情况是放在HTTP Body中。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 基础Request
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error {
NSURL *url = [NSURL URLWithString:URLString];
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
// NSURLRequest的参数配置
for (NSString *keyPath in self.mutableObservedChangedKeyPaths) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
// 处理参数
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}


// 把参数parameters设置到request的请求头和Query中
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 把请求头设置到Request中
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 处理请求参数
NSString *query = nil;
if (parameters) {
// 外界指定的序列化方式
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) *error = serializationError;
return nil;
}
} else {
// 默认的序列化方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters); // 键值对之间使用&拼接
break;
}
}
}
// 请求参数的位置
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
// 对哪些请求方式需要做query的编码, 默认是`GET`, `HEAD` 和 `DELETE`
if (query && query.length > 0) {
// 拼接Query到Request的URL中
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}

requestWithMethod:URLString:parameters:error:这个方法是对外的接口,用于创建普通的请求;requestBySerializingRequest:withParameters:error:这个方法是内部私有的,负责序列化参数。

普通的Request是这样处理的,特别地,对于AFJSONRequestSerializer和AFPropertyListRequestSerializer,区别就在于设置到HTTPBody中的Data格式不同。

1
2
3
4
5
6
7
// 构建JSON Data
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
[mutableRequest setHTTPBody:jsonData];

// 构建plist 对象
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error];
[mutableRequest setHTTPBody:plistData];

构建Request中比较关键的是如何处理请求的参数。 外界传入的参数是多种类型的,并不一定是字符串,可能是容器类型,比如字典(NSDictionary)、数组(NSArray)、集合(NSSet)等,对于复杂的参数,比如:
@{@"name": @"Jack", @"tag_list": @[@"apple", @"orange"], @"tag_info": @{@"super": @"Bob", @"child": @"Aaron"}}
处理后的结果是:
name=Jack&tag_info%5Bchild%5D=Aaron&tag_info%5Bsuper%5D=Bob&tag_list%5B%5D=apple&tag_list%5B%5D=orange
解码后是:
name=Jack&tag_info[child]=Aaron&tag_info[super]=Bob&tag_list[]=apple&tag_list[]=orange

知道结果了,再看其中的实现就比较容易了。对于NSDictionary,逐个解析,并把外键key和内键nestedKey结合,形成key[nestedKey]=nestedValue;对于NSArray,逐个拆解,形成key[]=nestedValue;对于NSSet,逐个遍历递归解析它的元素的类型;对于其他类型,key和value就已经确定了,做编码后的拼接:[NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]。实现逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}

对于下载类型的请求,因为没有参数(parameters),逻辑比较独立,借助NSInputStream和NSOutputStream实现数据下载。从HTTPBodyStream创建输入流,再从文件存储路径中创建输出流,在子线程以默认优先级实现流的转换。

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
49
50
51
// 构建下载Request
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(void (^)(NSError *error))handler {
// 从HTTPBodyStream创建输入流
NSInputStream *inputStream = request.HTTPBodyStream;
// 从指定下载路径创建输出流
NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
__block NSError *error = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
// read
NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
if (inputStream.streamError || bytesRead < 0) {
error = inputStream.streamError;
break;
}

// write
NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (outputStream.streamError || bytesWritten < 0) {
error = outputStream.streamError;
break;
}

if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}

// 下载完毕
[outputStream close];
[inputStream close];

if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
// 在主线程回传error
handler(error);
});
}
});

NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;
return mutableRequest;
}

对于上传类型的请求,它是需要参数(parameters)的,所以先利用构造普通请求的方式获取一个NSMutableURLRequest,再追加自己专有的上传逻辑。它创建了一个实现AFMultipartFormData协议的类AFStreamingMultipartFormData,由它负责接收相关参数和处理上传逻辑:
(1)创建AFStreamingMultipartFormData实例,把Query参数信息同步到这个AFStreamingMultipartFormData中。
(2)外界传入需要上传的数据,AFStreamingMultipartFormData处理上传逻辑。
(3)设置HTTPBodyStream、Content-Type、Content-Length等。

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
// 构建上传Request
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error {
// 创建普通请求
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
// 根据Request创建对应的AFStreamingMultipartFormData
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
// 把parameters字典拆分为pair模型 (field-value)
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
NSData *data = nil;
if ([pair.value isKindOfClass:[NSData class]]) {
data = pair.value;
} else if ([pair.value isEqual:[NSNull null]]) {
data = [NSData data];
} else {
data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
}
if (data) {
// 把parameters存到formData中
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
if (block) {
// 回传formData, 外界使用这个formData进一步拼接要上传的数据
block(formData);
}
// 设置HTTPBodyStream、Content-Type、Content-Length等
return [formData requestByFinalizingMultipartFormData];
}

外界拿到这个formData就可以这样使用:

1
[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"/Users/User/Desktop/main.png"] name:@"file" fileName:@"main.png" mimeType:@"image/png" error:nil];

这样来看,其实比较核心的就是AFStreamingMultipartFormData的实现。

AFStreamingMultipartFormData给外界暴露出多种方式的表单上传接口,接收各种参数,最终在内部创建一个AFHTTPBodyPart实例,AFHTTPBodyPart就是各种信息的聚合者并负责数据读取,接收各种类型的数据,这些不同类型的数据最终会统一为NSInputStream类型。最后AFStreamingMultipartFormData把AFHTTPBodyPart拼接到AFMultipartBodyStream中,由AFMultipartBodyStream负责整合所有的NSInputStream。

AFHTTPBodyPart接收必须的参数,用body属性存储”数据“,这个body可以是输入流(NSInputStream)、二进制数据(NSData)以及文件路径(NSURL)。

1
2
3
4
5
6
7
8
9
10
11
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = mutableHeaders;
bodyPart.boundary = self.boundary;
// 设置AFHTTPBodyPart的body为fileURL
// 设置AFHTTPBodyPart的body为inputStream:bodyPart.body = inputStream; 也可以设置AFHTTPBodyPart的body为data:bodyPart.body = data;
bodyPart.body = fileURL;
bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];

// 拼接bodyPart到AFMultipartBodyStream中
[self.bodyStream appendHTTPBodyPart:bodyPart];

最终把不同类型的数据统一为NSInputStream类型,并由这个NSInputStream负责上传数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据body创建输入流
- (NSInputStream *)inputStream {
if (!_inputStream) {
if ([self.body isKindOfClass:[NSData class]]) {
_inputStream = [NSInputStream inputStreamWithData:self.body];
} else if ([self.body isKindOfClass:[NSURL class]]) {
_inputStream = [NSInputStream inputStreamWithURL:self.body];
} else if ([self.body isKindOfClass:[NSInputStream class]]) {
_inputStream = self.body;
} else {
_inputStream = [NSInputStream inputStreamWithData:[NSData data]];
}
}
return _inputStream;
}

而继承自NSInputStream的AFMultipartBodyStream负责统筹所有的AFHTTPBodyPart中的输入流:

1
2
3
4
5
6
7
8
9
10
11
12
// 继承自NSInputStream
@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>
@property (nonatomic, assign) NSUInteger numberOfBytesInPacket;
@property (nonatomic, assign) NSTimeInterval delay;
@property (nonatomic, strong) NSInputStream *inputStream;
@property (readonly, nonatomic, assign) unsigned long long contentLength;
@property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;

- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;
- (void)setInitialAndFinalBoundaries;
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
@end

三、AFURLResponseSerialization

1,简单介绍

AFURLResponseSerialization是一种协议,定义了NSURLResponse转为NSObject对象的方式。

1
2
3
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

AFHTTPResponseSerializer作为通用的Response处理器,遵守该协议并实现了该协议的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
- (instancetype)init;
// 根据默认配置创建一个序列化对象
+ (instancetype)serializer;
// response可接受的状态码 http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
// response可接受的MIME类型, `Content-Type`字段的值
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
// 验证response和data的有效性, 最基础的实现是校验状态码和内容类型
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
@end

根据不同的Response类型,AFHTTPResponseSerializer分别派生出支持JSON类型的AFJSONResponseSerializer、支持XML类型的AFXMLParserResponseSerializer(生成NSXMLParser对象)和AFXMLDocumentResponseSerializer(生成NSXMLDocument对象)、支持属性列表文件类型的AFPropertyListResponseSerializer以及支持图片类型的AFImageResponseSerializer。比较特殊的是支持复合类型的AFCompoundResponseSerializer,它可以同时接受多种类型。

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
// AFJSONResponseSerializer用来校验和解码JSON response, 它的acceptableContentTypes默认有三种:`application/json`、`text/json`、`text/javascript`, 它的编码类型支持 UTF-8, UTF-16, 和 UTF-32
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
// 默认是NSJSONReadingMutableContainers
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
// 是否要移除JSON中value位`NSNull`的key, 默认为NO
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
// 根据指定的NSJSONReadingOptions创建一个序列化对象
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
@end

// AFXMLDocumentResponseSerializer用来校验和解码XML response并生成NSXMLDocument对象, 它的acceptableContentTypes默认有有种:`application/xml`、`text/xml`
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
// 对应NSXMLDocument的NSXMLNodeOptions, 默认是NSXMLNodeOptionsNone
@property (nonatomic, assign) NSUInteger options;
// 根据指定的NSXMLNodeOptionsNone创建一个序列化对象
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
@end

// AFPropertyListResponseSerializer用来校验和解码XML response并生成NSXMLDocument对象(就是解析plist文件), 它的acceptableContentTypes默认是:`application/x-plist`
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
// 格式, 默认是NSPropertyListXMLFormat_v1_0
@property (nonatomic, assign) NSPropertyListFormat format;
// 读取选项
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;
// 根据指定的NSPropertyListFormat和NSPropertyListReadOptions创建序列化对象
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions;
@end

// AFImageResponseSerializer用来校验和解码image response 它的acceptableContentTypes有:`image/tiff`、`image/jpeg`、`image/gif`、`image/png`、`image/ico`、`image/x-icon`、`image/bmp`、`image/x-bmp`、`image/x-xbitmap`、`image/x-win-bitmap`
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
// 图片的缩放比例, 默认是[[UIScreen mainScreen] scale]
@property (nonatomic, assign) CGFloat imageScale;
// 当与`setCompletionBlockWithSuccess:failure:`一起使用时,启用它可以显著提高iOS上的绘图性能,因为它允许在后台而不是在主线程上构建位图。默认为YES
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
#endif
@end

// AFCompoundResponseSerializer用来复合序列化器, 可以配置多个序列化对象一起解析, 只要有一个解析成功就返回
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer
// 配置多个序列化对象, 每个序列化对象必须是AFHTTPResponseSerializer的子类, 并且实现 `-validateResponse:data:error:`.方法
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
@end

2,AFHTTPResponseSerializer的实现

作为基类,AFHTTPResponseSerializer最通用的实现逻辑是校验Response和Data的有效性,这里是验证Content-Type和状态码是否在可接受范围之内。

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
- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error {
BOOL responseIsValid = YES;
NSError *validationError = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// 不在自己的content-type范围内, 抛出error
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
// 校验失败
responseIsValid = NO;
}

// 不在自己的status-code范围内, 抛出error
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];

if (data) mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
// 校验失败
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}

对于Response与Object的转化,基类AFHTTPResponseSerializer没有特别实现,直接返回了参数Data。

1
2
3
4
5
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error {
// 做校验
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}

而对于子类,不同之处就在于这里的Response与Object的转化。AFJSONResponseSerializer的处理是通过NSJSONSerialization把Response转为JSON对象;AFXMLParserResponseSerializer的处理是返回NSXMLParser实例;AFXMLDocumentResponseSerializer的处理是返回NSXMLDocument实例;AFPropertyListResponseSerializer的处理是通过NSPropertyListSerialization把Response转为plist对象。
AFImageResponseSerializer比较特殊,有两种处理方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
if (self.automaticallyInflatesResponseImage) {
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
} else {
return AFImageWithDataAtScale(data, self.imageScale);
}
#else
// Ensure that the image is set to it's correct pixel width and height
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
[image addRepresentation:bitimage];
return image;
#endif
return nil;
}

优化开关关闭时,直接把NSData转为UIImage;优化开关打开时,使用AFInflatedImageFromResponseWithDataAtScale()函数对把NSData转为UIImage。

最后是复合类型AFCompoundResponseSerializer的处理,它可以接收上述介绍的所有类型(的序列化器):只要有一个结果解析成功即可返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 - (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
// 必须是AFHTTPResponseSerializer的子类
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
NSError *serializerError = nil;
// 尝试序列化
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
if (error) {
*error = AFErrorWithUnderlyingError(serializerError, *error);
}
// 序列化成功即返回
return responseObject;
}
}
return [super responseObjectForResponse:response data:data error:error];
}

3,UIImage的优化

当我们调用UIImage的方法imageWithData:方法把数据转成UIImage对象后,其实这时UIImage对象还没准备好需要渲染到屏幕的数据,现在的网络图像PNG和JPG都是压缩格式,需要把它们解压转成bitmap后才能渲染到屏幕上,如果不做任何处理,当你把UIImage赋给UIImageView,在渲染之前底层会判断到UIImage对象未解压,没有bitmap数据,这时会在主线程对图片进行解压操作,再渲染到屏幕上。这个解压操作是比较耗时的,如果任由它在主线程做,可能会导致速度慢UI卡顿的问题。

当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。

AFImageResponseSerializer除了把返回数据解析成UIImage外,还会把图像数据解压,这个处理是在子线程(url_session_manager_processing_queue()),处理后上层使用返回的UIImage在主线程渲染时就不需要做解压这步操作,主线程减轻了负担,减少了UI卡顿问题。

具体实现上在AFInflatedImageFromResponseWithDataAtScale函数里,创建一个画布,把UIImage画在画布上,再把这个画布保存成UIImage返回给上层。只有JPG和PNG才会尝试去做解压操作,期间如果解压失败,或者遇到CMKY颜色格式的jpg,或者图像太大,就直接返回未解压的图像。

PNG或者JPEG压缩之后的图片文件会比同质量的位图小得多,直接或间接使用UIImageView,或者将图片绘制到CoreGraphics都需要对图片解压缩,对于一个较大的图片,都会占用一定的时间。这一步虽然不可避免,但是我们可以把这个操作放到后台线程,先把图片绘制到CGBitmapContext中,然后从Bitmap直接创建图片。

看看AFInflatedImageFromResponseWithDataAtScale()函数做了啥:

(1)创建位图上下文
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
(2)向位图上下文中绘制图片
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
(3)从位图上下文中取出CGImageRef的图片
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
(4)CGImageRef类型的图片转为UIImage类型
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];

下面是详细逻辑:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}

CGImageRef imageRef = NULL;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

// 根据不同的图片类型分别使用dataProvider创建相应的CGImageRef
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// CGImageCreateWithJPEGDataProvider不适合处理CMKY, 所以使用后续的AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
// 释放dataProvider内存
CGDataProviderRelease(dataProvider);

// 使用默认的处理方式创建UIImage,就是把NSData直接转UIImage
UIImage *image = AFImageWithDataAtScale(data, scale);
// dataProvider创建CGImageRef失败
if (!imageRef) {
// 如果是可动画图片(比如GIF)或者默认创建方式失败,直接返回默认图片
if (image.images || !image) {
return image;
}
// 返回image的copy
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}

// 大图片处理
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
// 像素过大或者每像素存储的字节太多,返回默认图片
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}

// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);

// RGB图片,调整bitmapInfo
if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
}

CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
// 清理颜色空间
CGColorSpaceRelease(colorSpace);
// 创建上下文失败,返回默认图片
if (!context) {
CGImageRelease(imageRef);
return image;
}
// 绘制图片
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
// 取出图片
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
// 转换为UIImage格式
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}

四、AFSecurityPolicy

1,基本简介

AFSecurityPolicy用来验证服务端证书有效性的类,上文已有介绍,可以借助它配置安全策略。主要有三种:

AFSSLPinningModeNone 这个模式本地没有保存证书,只验证服务器证书是不是系统受信任证书列表里的证书签发的。为默认。
AFSSLPinningModePublicKey 这个模式表示本地存有证书,只比对服务器证书和本地证书的Public Key是否一致。
AFSSLPinningModeCertificate 这个模式同样是本地存有证书,比对服务器证书和本地证书的所有内容,完全一致则信任服务器证书。

而AFSecurityPolicy做的工作就是分别对这三种安全策略做相应的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
// 默认为AFSSLPinningModeNone
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 本地证书的集合
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否允许无效的或者过期的证书,默认是NO
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证在证书中的CN字段中的 域名,默认是YES
@property (nonatomic, assign) BOOL validatesDomainName;
// 获取指定bundle下所有的证书
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
// 默认的安全策略: 即不允许无效证书(allowInvalidCertificates为NO)、验证域名(validatesDomainName为YES)、只验证服务器证书是不是系统受信任证书列表里的证书签发的(AFSSLPinningModeNone模式)
+ (instancetype)defaultPolicy;
// 指定模式初始化, 自动匹配main bundle中的.cer证书
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
// 指定模式、指定证书初始化
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
// 验证服务器证书是否能够被信任 serverTrust是X.509类型的证书(X.509是标准格式公钥证书) domain为服务端域名
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain;
@end

2,AFSecurityPolicy的实现

核心逻辑是evaluateServerTrust方法的实现。默认策略下(AFSSLPinningModeNone),只校验服务端证书的有效合法性:

1
2
3
4
5
6
7
8
9
10
11
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
#pragma clang diagnostic pop
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}

其中 kSecTrustResultProceed表示serverTrust验证成功,且该验证得到了用户认可(例如在弹出的是否信任的alert框中选择always trust)。 kSecTrustResultUnspecified表示 serverTrust验证成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书。两者取其一就可以认为对serverTrust验证成功。

详细的校验策略如下:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
// 本地证书为空、设置允许无效的或者过期的证书且只验证服务器证书是不是系统受信任证书列表里的证书签发的, 默认验证不通过
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
return NO;
}

NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
// 需要验证域名
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 不需要验证域名, 使用基础X.509策略
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 设置验证策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

if (self.SSLPinningMode == AFSSLPinningModeNone) {
// 如果允许无效的证书的就会直接返回YES, 不允许的话就会对服务端证书进行验证
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!self.allowInvalidCertificates && !AFServerTrustIsValid(serverTrust)) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
// 本地证书数组转为SecCertificateRef数组
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 本地证书与待信任serverTrust比对
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
// 服务端证书未验证通过
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
// 获取服务端(待信任serverTrust中)的证书
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
// 逐个判断, 有一个匹配到是相同的证书 即返回成功
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
// 只验证PublicKey
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
// 取出待信任serverTrust的PublicKey数组
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
// 与本地证书的PublicKey匹配, 一直则把计数器加一
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
// 表示PublicKey成功匹配的次数
return trustedPublicKeyCount > 0;
}
default:
return NO;
}
return NO;
}

五、AFNetworkReachabilityManager

1,基本使用

一般使用方式是这样,获取单例,设置回调,开始监测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusUnknown:
NSLog(@"不明网络");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"没有网络");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"蜂窝网络");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WiFi");
break;
default:
break;
}
}];
[manager startMonitoring];

接口比较简单,有这些:

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
@interface AFNetworkReachabilityManager : NSObject
// 网络状态
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
// 网络可用
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
// 移动网络
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
// WIFI网络
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
// 单例
+ (instancetype)sharedManager;
// 创建一个实例对象 default socket address
+ (instancetype)manager;
// 创建一个监听指定域名 的网络状态监测器
+ (instancetype)managerForDomain:(NSString *)domain;
// 根据指定的socket address(sockaddr_in6)创建一个网络状态监测器
+ (instancetype)managerForAddress:(const void *)address;
// 根据指定的__SCNetworkReachability创建一个网络状态监测器
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
// 开始监测
- (void)startMonitoring;
// 停止监测
- (void)stopMonitoring;
// 返回网络状态的字符串形式
- (NSString *)localizedNetworkReachabilityStatusString;
// 设置网络状态改变的回调
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
// 禁用
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
@end

2,实现

外界可以指定域名或者套接字地址创建网络状态监听器,默认创建的监听器监测所有域名状态,最终生成SCNetworkReachabilityRef实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 根据域名创建SCNetworkReachabilityRef
+ (instancetype)managerForDomain:(NSString *)domain {
// NSString转char *, 直接使用UTF8String方法即可
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}

// 根据套接字地址创建SCNetworkReachabilityRef
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}

当开启网络状态监测时,设置SCNetworkReachabilityRef实例的回调,并创建Runloop,设置Runloop为Common Mode,在回调中处理自己的逻辑。可见,网络状态监测的功能底层是靠SCNetworkReachabilityRef实现的。

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
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) return;
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;

strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
// 把manager透传
return strongSelf;
};

// 最核心的逻辑
// 设置callback的retain release, 内存管理
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
// 回调是这个AFNetworkReachabilityCallback()函数
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
// 在main runloop的common mode持续运行
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
// 调用startMonitoring方法, 立即在低优先级队列中发出网络状态通知
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}

在网络状态改变时,之前注册的回调被调用,首先外界的状态回调networkReachabilityStatusBlock随之被调用,接着在主线程发出网络状态改变的通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusCallback block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
AFNetworkReachabilityManager *manager = nil;
if (block) {
manager = block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:manager userInfo:userInfo];
});
}

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusCallback)info);
}

当停止网络状态监听时,SCNetworkReachabilityRef从Runloop中移除。

1
2
3
4
5
- (void)stopMonitoring {
if (!self.networkReachability) return;
// 把SCNetworkReachabilityRef从runloop中移除
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

六、总结

下面是总结出来的有意思的点。

1,AFURLSessionManagerTaskDelegate的职责分明
AFURLSessionManager是NSURLSession及所有的Task的代理,但是如果由它自己处理所有的代理逻辑,不免繁重。作者另建一个AFURLSessionManagerTaskDelegate,专门负责Task的代理工作,把自己接收到的代理消息转发给TaskDelegate,从而职责清晰,逻辑简洁。
我们自己在业务开发中肯定也会遇到这种情况,比如UITableViewDelegate、WKNavigationDelegate等,像这些代理方法都不是个位数,如果完全塞到控制器中,整个逻辑不会特别清晰,这时,新建一个职责单一的代理类来处理这些逻辑就显得非常必要了。

2,异步转同步标准写法
比较常见了。

1
2
3
4
5
6
7
8
9
10
11
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
}
...
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;

3,给任意对象添加附加值
这个附加值就是属性。一般来说,关联属性常用在“给分类添加属性”的场景,实现某个功能的即插即用。但是不局限于此,我们完全可以利用关联属性给直接任意对象添加附加值,比如当产生error时,把error附加到Task上,需要的时候再取出来:

1
2
objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN);
error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;

4,处理证书的质询
绝大多数情况只验证服务器证书是不是系统受信任证书列表里的证书签发的即可,借助AFSecurityPolicy可以这么处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([[AFSecurityPolicy defaultPolicy] evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 执行安全策略成功 安装该证书
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
}
} else {
// 取消请求, 证书忽略
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}

if (completionHandler) {
completionHandler(disposition, credential);
}
}

5,多读单写
栅栏函数+并发队列实现多读单写:
(1)可以同时有多个读操作,读操作可以并发执行。
(2)不能同时有多个写操作,写操作串行执行,在写操作之前,读操作都完成。
(3)写操作与读操作、其他写操作是互斥的(读操作的时候,不能有写操作,写操作的时候,不能有读操作)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}

- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
// 这里是同步写操作, 异步也可以
dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}

6,迪米特法则
对外暴露方法而非类本身。比如这个接口的设计:

1
2
3
4
5
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;

外界只需要关注formData怎么使用,而不必关注formData是什么、有什么属性。这样做降低了类之间的耦合度,提高了模块的相对独立性。
我们在日常开发的时候也可以借鉴,通过协议告诉外界,这个“NSObject”对象怎么使用。

7,UIImage的线程安全
NSData转UIImage时,使用imageWithData:方法是非线程安全的,需要加锁。initWithCGImage:方法是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+ (UIImage *)af_safeImageWithData:(NSData *)data {
UIImage* image = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init];
});

[imageLock lock];
image = [UIImage imageWithData:data];
[imageLock unlock];
return image;
}

// CGImage方式是线程安全的
[[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];

7,优化UIImage的加载
子线程预解压,当然也可以用AFN现成的方法AFInflatedImageFromResponseWithDataAtScale()

1
2
3
4
5
6
7
8
 (1)创建位图上下文
`CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);`
(2)向位图上下文中绘制图片
`CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);`
3)从位图上下文中取出CGImageRef的图片
`CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);`
4CGImageRef类型的图片转为UIImage类型
`UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];`