介紹
上篇文章有提到AFURLSessionManager這個(gè)類,在AF中這個(gè)類是對(duì)NSURLSession進(jìn)行的封裝,在使用NSURLSession需要知道使用套路。
- 首先創(chuàng)建會(huì)話
- 通過(guò)會(huì)話和請(qǐng)求獲取任務(wù)。
- 每一個(gè)網(wǎng)絡(luò)請(qǐng)求被封裝成一個(gè)任務(wù)。
- 通過(guò)任務(wù)進(jìn)行網(wǎng)絡(luò)通信。
通過(guò)上述的介紹,有兩個(gè)關(guān)鍵詞,會(huì)話、請(qǐng)求、任務(wù)
會(huì)話:NSURLSession(通過(guò)會(huì)話我們可以獲取對(duì)應(yīng)的請(qǐng)求任務(wù)).
請(qǐng)求:NSURLRequest.
任務(wù):NSURLSessionDataTask、NSURLSessionUploadTask
、NSURLSessionDownloadTask、NSURLSessionStreamTask.
任務(wù)有四種,分別是數(shù)據(jù)任務(wù),上傳任務(wù),下載任務(wù),流式任務(wù)。
他們的繼承關(guān)系:
NSURLSessionDataTask: NSURLSessionTask
NSURLSessionUploadTask : NSURLSessionDataTask
NSURLSessionDownloadTask : NSURLSessionTask
他們的代理關(guān)系:
NSURLSessionTaskDelegate <NSURLSessionDelegate>
NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
AFURLSessionManager
在AFURLSessionManager類文件中我們可以看到其中還包含了AFURLSessionManagerTaskDelegate和_AFURLSessionTaskSwizzling.這兩個(gè)類。
AFURLSessionManagerTaskDelegate
AFURLSessionManagerTaskDelegate是對(duì)請(qǐng)求任務(wù)的另一層封裝。主要處理請(qǐng)求數(shù)據(jù)相關(guān)業(yè)務(wù),比如說(shuō)上傳下載的進(jìn)度、下載獲取的數(shù)據(jù)、下載完成的回調(diào)等。AFURLSessionManagerTaskDelegate和AFURLSessionManager的關(guān)系,請(qǐng)看下圖。

在AFURLSessionManagerTaskDelegate類中也實(shí)現(xiàn)了NSURLSession的相關(guān)代理方法。
#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
上述這里代理方法的調(diào)用,并不是系統(tǒng)調(diào)用,而是通過(guò)AFURLSessionManager中的回調(diào)方法,和 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
通過(guò)對(duì)象主動(dòng)調(diào)用,處理內(nèi)部數(shù)據(jù)。
_AFURLSessionTaskSwizzling
這個(gè)私有的內(nèi)部類主要解決iOS 7和iOS 8在NSURLSessionTask實(shí)現(xiàn)方面的不同,而引發(fā)的bug,在AF中對(duì)于任務(wù)的開(kāi)始和暫停是需要進(jìn)行監(jiān)聽(tīng)的。
- 簡(jiǎn)單地引用
[NSURLSessionTask class]將不起作用。你需要讓一個(gè)NSURLSession來(lái)實(shí)際創(chuàng)建一個(gè)對(duì)象,并從那里獲取類。 - 在iOS 7上,
localDataTask是一個(gè)__NSCFLocalDataTask,它繼承自__NSCFLocalSessionTask,它繼承自__NSCFURLSessionTask。 - 在iOS 8上,
localDataTask是一個(gè)__NSCFLocalDataTask,它繼承自__NSCFLocalSessionTask,它繼承自NSURLSessionTask。 - 在iOS 7上,
__ NSCFLocalSessionTask和__NSCFURLSessionTask是唯一具有自己的resume和suspend實(shí)現(xiàn)的兩個(gè)類,而__NSCFLocalSessionTask`則不會(huì)調(diào)用超級(jí)。這意味著兩個(gè)類都需要調(diào)整。 - 在iOS 8上,
NSURLSessionTask是唯一實(shí)現(xiàn)resume和suspend的類。這意味著這是唯一需要調(diào)整的類。 - 因?yàn)槊總€(gè)iOS版本的類層次結(jié)構(gòu)都不涉及
NSURLSessionTask,所以更容易將混合方法添加到虛擬類并在那里管理它們。
AF給出的解決方案
- 沒(méi)有實(shí)現(xiàn)
resume或suspend調(diào)用super。如果要在未來(lái)版本的iOS中進(jìn)行更改,我們需要處理它。 - 沒(méi)有后臺(tái)任務(wù)類覆蓋
resume或suspend
1)通過(guò)向數(shù)據(jù)任務(wù)詢問(wèn)“NSURLSession”實(shí)例來(lái)獲取“__NSCFLocalDataTask”的實(shí)例。
2)抓住指向af_resume的原始實(shí)現(xiàn)的指針
3)檢查當(dāng)前類是否具有resume的實(shí)現(xiàn)。如果是,請(qǐng)繼續(xù)執(zhí)行步驟4。
4) 獲取當(dāng)前類的父類
5) 將當(dāng)前類的指針抓取到resume的當(dāng)前實(shí)現(xiàn)。
6) 將超類的指針抓到resume的當(dāng)前實(shí)現(xiàn)中。
7) 如果resume的當(dāng)前類實(shí)現(xiàn)不等于resume的超類實(shí)現(xiàn)并且resume的當(dāng)前實(shí)現(xiàn)不等于af_resume的原始實(shí)現(xiàn),那么swizzle方法
8) 將當(dāng)前類設(shè)置為超類,并重復(fù)步驟3-8
以上是在AFURLSessionManager直譯的,可以通過(guò)源碼查看一下。
總結(jié)一下:
因?yàn)椴煌姹镜膶?shí)現(xiàn)不一致,可能會(huì)導(dǎo)致無(wú)法監(jiān)聽(tīng)到resume、suspend會(huì)出現(xiàn)問(wèn)題,那么通過(guò)運(yùn)行時(shí)將獲取方法的指針,交換方法。用_AFURLSessionTaskSwizzling類中實(shí)現(xiàn)的方法,替換系統(tǒng)的方法,規(guī)避版本實(shí)現(xiàn)不同而導(dǎo)致的問(wèn)題。
下面解析一下源碼(程序在啟動(dòng)后就會(huì)系統(tǒng)的resume、suspend就會(huì)被替換)
+ (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"
// 創(chuàng)建一個(gè)對(duì)象
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
// 通過(guò)對(duì)象獲取具體的類
Class currentClass = [localDataTask class];
// 3. 檢查當(dāng)前類是否具有resume的實(shí)現(xiàn)
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4. 獲取當(dāng)前類的父類
Class superClass = [currentClass superclass];
// 指向函數(shù)的指針
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];
}
// 當(dāng)前類指向父類
currentClass = [currentClass superclass];
}
// 取消任務(wù)
[localDataTask cancel];
// 完成任務(wù)和檢測(cè)
[session finishTasksAndInvalidate];
}
}
- (void)af_resume { // 開(kāi)始會(huì)話
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
// 調(diào)用系統(tǒng)的
[self af_resume];
// 發(fā)起通知
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
- (void)af_suspend { // 暫停會(huì)話
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
// 調(diào)用系統(tǒng)的
[self af_suspend];
// 發(fā)起通知
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
當(dāng)外界一旦調(diào)用[dataTask resume];就會(huì)進(jìn)入該類自己實(shí)現(xiàn)的方法中,發(fā)起通知。這樣就可以監(jiān)聽(tīng)開(kāi)始和暫停了。
