介绍
上篇文章有提到AFURLSessionManager
这个类,在AF中这个类是对NSURLSession
进行的封装,在使用NSURLSession
需要知道使用套路。
- 首先创建会话
- 通过会话和请求获取任务。
- 每一个网络请求被封装成一个任务。
- 通过任务进行网络通信。
通过上述的介绍,有两个关键词,会话、请求、任务
会话:NSURLSession
(通过会话我们可以获取对应的请求任务).
请求:NSURLRequest
.
任务:NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
、NSURLSessionStreamTask
.
任务有四种,分别是数据任务,上传任务,下载任务,流式任务。
他们的继承关系:
NSURLSessionDataTask: NSURLSessionTask
NSURLSessionUploadTask : NSURLSessionDataTask
NSURLSessionDownloadTask : NSURLSessionTask
他们的代理关系:
NSURLSessionTaskDelegate <NSURLSessionDelegate>
NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
AFURLSessionManager
在AFURLSessionManager
类文件中我们可以看到其中还包含了AFURLSessionManagerTaskDelegate
和_AFURLSessionTaskSwizzling
.这两个类。
AFURLSessionManagerTaskDelegate
AFURLSessionManagerTaskDelegate是对请求任务的另一层封装。主要处理请求数据相关业务,比如说上传下载的进度、下载获取的数据、下载完成的回调等。AFURLSessionManagerTaskDelegate
和AFURLSessionManager
的关系,请看下图。
在AFURLSessionManagerTaskDelegate
类中也实现了NSURLSession的相关代理方法。
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
上述这里代理方法的调用,并不是系统调用,而是通过AFURLSessionManager
中的回调方法,和 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
通过对象主动调用,处理内部数据。
_AFURLSessionTaskSwizzling
这个私有的内部类主要解决iOS 7和iOS 8在NSURLSessionTask实现方面的不同,而引发的bug,在AF中对于任务的开始和暂停是需要进行监听的。
- 简单地引用
[NSURLSessionTask class]
将不起作用。你需要让一个NSURLSession
来实际创建一个对象,并从那里获取类。 - 在iOS 7上,
localDataTask
是一个__NSCFLocalDataTask
,它继承自__NSCFLocalSessionTask
,它继承自__NSCFURLSessionTask
。 - 在iOS 8上,
localDataTask
是一个__NSCFLocalDataTask
,它继承自__NSCFLocalSessionTask
,它继承自NSURLSessionTask
。 - 在iOS 7上,
__ NSCFLocalSessionTask
和__NSCFURLSessionTask
是唯一具有自己的resume
和suspend
实现的两个类,而__NSCFLocalSessionTask`则不会调用超级。这意味着两个类都需要调整。 - 在iOS 8上,
NSURLSessionTask
是唯一实现resume
和suspend
的类。这意味着这是唯一需要调整的类。 - 因为每个iOS版本的类层次结构都不涉及
NSURLSessionTask
,所以更容易将混合方法添加到虚拟类并在那里管理它们。
AF给出的解决方案
- 没有实现
resume
或suspend
调用super。如果要在未来版本的iOS中进行更改,我们需要处理它。 - 没有后台任务类覆盖
resume
或suspend
1)通过向数据任务询问“NSURLSession”实例来获取“__NSCFLocalDataTask”的实例。
2)抓住指向af_resume
的原始实现的指针
3)检查当前类是否具有resume的实现。如果是,请继续执行步骤4。
4) 获取当前类的父类
5) 将当前类的指针抓取到resume
的当前实现。
6) 将超类的指针抓到resume
的当前实现中。
7) 如果resume
的当前类实现不等于resume
的超类实现并且resume
的当前实现不等于af_resume
的原始实现,那么swizzle方法
8) 将当前类设置为超类,并重复步骤3-8
以上是在AFURLSessionManager
直译的,可以通过源码查看一下。
总结一下:
因为不同版本的实现不一致,可能会导致无法监听到resume
、suspend
会出现问题,那么通过运行时将获取方法的指针,交换方法。用_AFURLSessionTaskSwizzling
类中实现的方法,替换系统的方法,规避版本实现不同而导致的问题。
下面解析一下源码(程序在启动后就会系统的resume
、suspend
就会被替换)
+ (void)load {
/**
WARNING: Trouble Ahead
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
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];
// 3. 检查当前类是否具有resume的实现
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4. 获取当前类的父类
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];
}
}
- (void)af_resume { // 开始会话
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
// 调用系统的
[self af_resume];
// 发起通知
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
- (void)af_suspend { // 暂停会话
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
// 调用系统的
[self af_suspend];
// 发起通知
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
当外界一旦调用[dataTask resume];
就会进入该类自己实现的方法中,发起通知。这样就可以监听开始和暂停了。