AFNetworking3源码阅读-NSURLSession

AFURLSessionManager 和 AFHTTPSessionManager 是AFNetworking的核心类
这里先阅读AFURLSessionManager。

使用 AFURLSessionManager 创建下载任务

1
2
3
4
5
6
7
8
9
10
11
12
13
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];

AFURLSessionManager 初始化

initWithSessionConfiguration: 创建和管理 NSURLSession

1
2
3
4
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration{
1. 初始化相应的属性
2. 为task设置代理
}

1. 初始化相应的属性

  • 初始化会话配置(NSURLSessionConfiguration),默认为defaultSessionConfiguration
  • 初始化会话(NSURLSession),设置会话的配置,代理以及队列
  • 初始化响应序列化(AFJSONResponseSerializer),安全认证(AFSecurityPolicy),网络状态监控(AFNetworkReachabilityManager)
  • 初始化保存 data task 的字典(mutableTaskDelegatesKeyedByTaskIdentifier)

2. 为task设置代理

使用getTasksWithCompletionHandler从session中取出task,并为task设置代理。

管理NSURLSessionTask

AFURLSessionManager初始化后,就可以获取NSURLSessionTask对象

AFNetworking3 中有7个方法分别返回NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。这里从以下方法来看是如何实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
__block NSURLSessionUploadTask *uploadTask = nil;
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});

[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];

return uploadTask;
}

其中 url_session_manager_create_task_safely是解决iOS8之前的一个bug ,详情可以看 Issue 2093

  • 通过传入的NSURLRequest生成NSURLSessionUploadTask对象
  • 调用自定义方法 addDelegateForUploadTask...来为task添加回调事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (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 类实现了 NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate代理。
  • 使用 setDelegate来设置代理
1
2
3
4
5
6
7
8
9
10
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
#检查参数

[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}

AFURLSessionManager 就是通过字典 mutableTaskDelegatesKeyedByTaskIdentifier 来存储并管理每一个 NSURLSessionTask,它以 taskIdentifier 为键存储 task。
该方法使用 NSLock 来保证不同线程使用 mutableTaskDelegatesKeyedByTaskIdentifier 时,不会出现线程竞争的问题。

实现 NSURLSessionDelegate

AFURLSessionManager初始化的时候调用 initWithSessionConfiguration,将 NSURLSession 的代理设置为self

AFURLSessionManager实现了这些协议:

  • NSURLSessionDelegate
  • NSURLSessionTaskDelegate
  • NSURLSessionDataDelegate
  • NSURLSessionDownloadDelegate

所有的delegate 实现方法都提供对应的block接口,比如:

1
2
3
4
5
6
7
8
9
10
11
#pragma mark - NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}

[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

对应的block接口是:

1
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;

使用 AFURLSessionManagerTaskDelegate 管理进度

上文的AFURLSessionManagerTaskDelegate实现了 NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate代理。
它主要为 task 提供进度管理功能,并在 task 结束时回调,这里开始查看如何实现的。

1
2
3
4
5
- (instancetype)initWithTask:(NSURLSessionTask *)task {

1. 设置上传或者下载任务状态改变的是回调
2. KVO
}

1. 设置上传或者下载任务状态改变的是回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
if ([progress respondsToSelector:@selector(setResumingHandler:)]) {
progress.resumingHandler = ^{
[weakTask resume];
};
}

通过对应的NSProgress状态改变时,调用 resume suspend等方法改变 task 的状态

2. KVO

1
2
3
4
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
1
2
3
4
5
6
7
8
9
10
11
12
- (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);
}
}
}

对progress进行键值观察,对象的某些属性改变时更新 NSProgress 对象或使用 block 传递 NSProgress 对象 self.uploadProgressBlock(object)或者 self.downloadProgressBlock(object);。其中fractionCompleted表示 NSProgress的进度

代理方法 URLSession:task:didCompleteWithError:

当task完成数据传输时会调用这个方法,接下来看一下在 AFURLSessionManagerTaskDelegate中的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
1. 取出数据。储存responseSerializer downloadFileURL

if(error){
2. 有错误时调用completionHandler
}
else{
3. 无错误时调用completionHandler
}
}

1. 取出数据。储存responseSerializer downloadFileURL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__strong AFURLSessionManager *manager = self.manager;

__block id responseObject = nil;

__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}

if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}

这部分代码从 mutableData 中取出了数据,设置了 userInfo。其中 self. mutableData是从
URLSession:dataTask:didReceiveData代理方法中获取的。userInfo 用来做通知时传送的数据

2. 3.调用completionHandler

该代理方法中的error仅指本地端的错误,比如无法解析域名或者不能连上host等。

1
2
3
4
5
6
7
8
9
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];
});
});

如果当前 manager 持有 completionGroup 或者 completionQueue 就使用它们。否则会创建一个 dispatch_group_t 并在主线程中调用 completionHandler 并发送通知(在主线程中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}

if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}

if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}

如果没有error,先对数据进行序列化,然后同样调用 block 并发送通知。

_AFURLSessionTaskSwizzling

因为 iOS7 和 iOS8 上对于 NSURLSessionTask 的实现不同,AFURLSessionTaskSwizzling 的就是修改 NSURLSessionTask 的 resume 和 suspend 方法

使用+load 替换方法

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)load{

if (NSClassFromString(@"NSURLSessionTask")) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];

while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}

[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
  1. 首先用 NSClassFromString(@”NSURLSessionTask”) 判断当前部署的 iOS 版本是否含有类 NSURLSessionTask
  2. 因为 iOS7 和 iOS8 上对于 NSURLSessionTask 的实现不同,所以会通过 - [NSURLSession dataTaskWithURL:] 方法返回一个 NSURLSessionTask 实例
  3. 取得当前类 _AFURLSessionTaskSwizzling 中的实现 af_resume
  4. 如果当前类 currentClass 有 resume 方法
    • 真:5
    • 假:6
  5. 使用 swizzleResumeAndSuspendMethodForClass: 调剂该类的 resume 和 suspend 方法
  6. currentClass = [currentClass superclass]

使用 AFSecurityPolicy 保证请求的安全

AFSecurityPolicy 具体参考 AFNetworking3源码阅读-Security

在代理方法 URLSession:didReceiveChallenge:completionHandler中调用

1
2
3
...
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
...