參考:AFNetworking 3.0 源碼解讀(五)之 AFURLSessionManager
說明:很多內容都是摘抄原文纬向,只是根據自己的需要進行摘抄或者總結涯雅,如有不妥請及時指出儒陨,謝謝。
這次主要總結AFURLSessionManager张症,下一篇會總結 AFHTTPSessionManager 能庆,它是AFURLSessionManager的一個子類。
其實AFURLSessionManager創(chuàng)建并管理著NSURLSession這個對象,而NSURLSession又基于NSURLSessionConfiguration宣蠕。
AFURLSessionManager實現(xiàn)了4個協(xié)議
1例隆、NSURLSessionDelegate
URLSession:didBecomeInvalidWithError:
URLSession:didReceiveChallenge:completionHandler:
URLSessionDidFinishEventsForBackgroundURLSession:
2、NSURLSessionTaskDelegate
URLSession:willPerformHTTPRedirection:newRequest:completionHandler:
URLSession:task:didReceiveChallenge:completionHandler:
URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
URLSession:task:needNewBodyStream:
URLSession:task:didCompleteWithError:
3抢蚀、NSURLSessionDataDelegate
URLSession:dataTask:didReceiveResponse:completionHandler:
URLSession:dataTask:didBecomeDownloadTask:
URLSession:dataTask:didReceiveData:
URLSession:dataTask:willCacheResponse:completionHandler:
4镀层、NSURLSessionDownloadDelegate
URLSession:downloadTask:didFinishDownloadingToURL:
URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:
URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
這個方法是指定初始化方法,什么叫指定初始化方法皿曲?
NS_DESIGNATED_INITIALIZER
這個宏告訴開發(fā)者唱逢,如果寫一個繼承A類的子類B,那么就要調用父類A的指定的初始化方法谷饿。舉個例子:
@interface MyClass : NSObject @property(copy, nonatomic) NSString *name;
-(instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
-(instancetype)init;
如果定義一個ClassB : MyClass惶我,那么ClassB中就必須實現(xiàn)-(instancetype)initWithName:(NSString *)name這個方法,否則就會收到警告博投。
/** Invalidates the managed session, optionally canceling pending tasks. @param cancelPendingTasks Whether or not to cancel pending tasks.
根據是否取消未完成的任務來使session失效
*/
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
NSURLSession有兩個方法:
1绸贡、-(void)finishTasksAndInvalidate; 標示待完成所有的任務后失效
2、-(void)invalidateAndCancel; 標示 立即失效毅哗,未完成的任務也將結束
接下來看.m中的內容
#ifndef NSFoundationVersionNumber_iOS_8_0
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11
#else
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
通過這個宏定義听怕,我們可以聯(lián)想到判斷iOS版本號的幾個方法
1、[UIDevice currentDevice].systemVersion
2虑绵、通過比較Foundation中的宏定義
3尿瞭、通過在某系版本中新出現(xiàn)的方法來判斷,UIAlertController 這個類是iOS8之后才出現(xiàn)的 NS_CLASS_AVAILABLE_IOS(8_0),如果當前系統(tǒng)版本沒有這個類NSClassFromString(@"UIAlertController" == (null),從而判斷當前版本是否大于等于iOS8
AFN中所有的和創(chuàng)建任務相關的事件都放到了一個單例隊列中翅睛,而且是串行的声搁。
從名字就可以看出,這是一個安全的創(chuàng)建任務的方法
知識點:dispatch_block_t
系統(tǒng)是這么定義的
typedef void (^dispatch_block_t)(void);
關于這個block我們要注意以下幾點:
1捕发、MRC情況下疏旨,使用完要記得release
2、生命block時扎酷,它是被分配到棧上的檐涝,使用時,我們需要把block拷貝到堆上面法挨,因為棧是系統(tǒng)管理的谁榜,隨時可能被釋放。
這個方法是用來創(chuàng)建一個隊列來管理數(shù)據的處理凡纳,同上面的方法類似窃植,不過這個創(chuàng)建的是并發(fā)的,加快的數(shù)據處理的速度惫企。
AFURLSessionManagerTaskDelegate
這個代理的目的:
1撕瞧、處理上傳或者下載的進度
2陵叽、處理獲取完數(shù)據后的行為
需要說一下NSProgress,它內部是使用kvo實現(xiàn)的丛版,我們應該盡量向這個類靠攏巩掺。
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
__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;
}
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];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
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];
});
});
});
}
}
NSProgress 通過監(jiān)聽fractionCompleted這個屬性來獲取進度
移除kvo以及監(jiān)聽對象
在這里要說一下關于task四個代理的調用問題。
task一共有4個delegate页畦,只要設置了一個胖替,就代表四個全部設置,有時候一些delegate不會被觸發(fā)的原因在于這四種delegate是針對不同的URLSession類型和URLSessionTask類型來進行響應的豫缨,也就是說不同的類型只會觸發(fā)這些delegate中的一部分独令,而不是觸發(fā)所有的delegate。
舉例如下:
1好芭、觸發(fā)NSURLSessionDataDelegate
//使用函數(shù)dataTask來接收數(shù)據
-(void)URLSession:(NSURLSession *)session dataTask:
(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
//則NSURLSession部分的代碼如下
NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"];
NSURLSessionDataTask* dataTask=[session dataTaskWithURL:url];
[dataTask resume];
2燃箭、觸發(fā)NSURLSessionDownloadDelegate
//使用函數(shù)downloadTask來接受數(shù)據
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
//則NSURLSession部分的代碼如下
NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"];
NSURLSessionDownloadTask* dataTask=[session downloadTaskWithURL:url];
[dataTask resume];
這兩段代碼的主要區(qū)別在于NSURLSessionTask的類型的不同,造成了不同的Delegate被觸發(fā).
#pragma mark - NSURLSessionDataTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
--
#pragma mark - NSURLSessionTaskDelegate
(void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
__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;
}
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];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
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];
});
});
});
}
}
這個方法是獲取數(shù)據完成了方法舍败。最終通過self.completionHandler和** [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];**這兩個手段來傳遞數(shù)據和事件
我們主要看看這個通知的userinfo會有那些信息:
AFNetworkingTaskDidCompleteResponseSerializerKey -> manager.responseSerializer
AFNetworkingTaskDidCompleteAssetPathKey -> self.downloadFileURL
AFNetworkingTaskDidCompleteResponseDataKey -> data
AFNetworkingTaskDidCompleteErrorKey -> error
AFNetworkingTaskDidCompleteSerializedResponseKey -> responseObject
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
這個方法在下載完成后會調用招狸。之前有一個使用場景,就是視頻邊下載邊播放邻薯。要求在視頻在下載完之前拿到正在下載的數(shù)據裙戏。ASI有一個屬性能夠拿到fileURL,AFNetworking卻沒有這個屬性厕诡,現(xiàn)在看來,通過設置
- (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block {
self.downloadTaskDidFinishDownloading = block;
}
這樣可以把數(shù)據寫入我們指定的地方
_AFURLSessionTaskSwizzling
當時看這個私有類的時候一直想不通為什么要弄一個這樣的類呢累榜?首先看了AFNetworking給出的解釋https://github.com/AFNetworking/AFNetworking/pull/2702 大概說了當初這個私有類的由來,ios7和ios8 task的父類并不一樣灵嫌,關鍵是resume
and suspend
這兩個方法的調用壹罚。
因此,AFNetworking 利用Runtime交換了resume
and suspend
的方法實現(xiàn)寿羞。在替換的方法中發(fā)送了狀態(tài)的通知渔嚷。這個通知被使用在UIActivityIndicatorView+AFNetworking
這個UIActivityIndicatorView的分類中。
還有值得說的是 + (void)load這個方法稠曼,這個方法會在app啟動時加載所有類的時候調用,且只會調用一次,所以這就有了使用場景了客年,當想使用運行時做一些事情的時候霞幅,就能夠用上這個方法了
舉幾個使用這個方法的例子:
下邊就看看代碼部分:
//根據兩個方法名稱交換兩個方法,內部實現(xiàn)是先根據函數(shù)名獲取到對應方法實現(xiàn)
//在調用method_exchangeImplementations交換兩個方法
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
//給theClass添加方法量瓜,方法名為selector司恳,實現(xiàn)為method
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
--
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
//因為af_resume、af_suspend都是類的實例方法绍傲,所以直接調用class_getInstanceMethod獲取這兩個方法
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
//給theClass添加一個名稱為@selector(af_resume)的方法扔傅,方法實現(xiàn)為afResumeMethod
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
//同上
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
--
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
//經過method swizzling之后耍共,這里調用af_resume不會引起死循環(huán),因為相當于調用的是系統(tǒng)的resume
[self af_resume];
//如果state是其它狀態(tài)猎塞,則發(fā)出通知改編成resume狀態(tài)
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];
}
}
--
+ (void)load {
/**
WARNING: Trouble Ahead
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
Many Unit Tests have been built to validate as much of this behavior has possible.
Here is what we know:
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
Some Assumptions:
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
- No background task classes override `resume` or `suspend`
The current solution:
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
2) Grab a pointer to the original implementation of `af_resume`
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
4) Grab the super class of the current class.
5) Grab a pointer for the current class to the current implementation of `resume`.
6) Grab a pointer for the super class to the current implementation of `resume`.
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
8) Set the current class to the super class, and repeat steps 3-8
翻譯:iOS7和iOS8在NSURLSessionTask實現(xiàn)上有些不同试读,這使得下面的實現(xiàn)略顯復雜
關于這個問題,大家做了很多Uint Test荠耽,足以證明這個方法是可行的
目前我們所知:
- NSURLSessionTasks是一組class的統(tǒng)稱钩骇,如果你僅僅使用系統(tǒng)提供的api來獲取NSURLSessionTasks的class,并不一定返回的是你想要的那個(獲取NSURLSessionTask的class目的是為了獲取其resume方法)
-簡單的使用[NSURLSessionTask class]并不起作用铝量,你需要新建一個NSURLSession倘屹,并根據創(chuàng)建的session再構建出一個NSURLSessionTask對象才行。
-iOS7上localDataTask(下面代碼構造出的NSURLSessionDataTask類型的變量慢叨,為了獲取對應Class)的類型是 __NSCFLocalDataTask纽匙,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask。
-iOS 8上拍谐,localDataTask的類型為__NSCFLocalDataTask烛缔,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自NSURLSessionTask
-iOS 7上赠尾,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現(xiàn)了resume和suspend方法的類力穗,另外__NSCFLocalSessionTask中的resume和suspend并沒有調用其父類(即__NSCFURLSessionTask)方法,這也意味著兩個類的方法都需要進行method swizzling气嫁。
-iOS 8上当窗,NSURLSessionTask是唯一實現(xiàn)了resume和suspend方法的類。這也意味著其是唯一需要進行method swizzling的類
-因為NSURLSessionTask并不是在每個iOS版本中都存在寸宵,所以把這些放在此處(即load函數(shù)中)崖面,比如給一個dummy class添加swizzled方法都會變得很方便,管理起來也方便梯影。
一些假設前提:
-目前iOS中resume和suspend的方法實現(xiàn)中并沒有調用對應的父類方法巫员。如果日后iOS改變了這種做法,我們還需要重新處理
- 沒有哪個后臺task會重寫resume和suspend函數(shù)
*/
//1甲棍、先構建一個NSURLSession對象session简识,在通過session構建出一個_NSCFLocalDataTask變量
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
//2、獲取到af_resume指針
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
//3感猛、檢查當前類是否實現(xiàn)了af_resume七扰,如果實現(xiàn)了,繼續(xù)第4步
while (class_getInstanceMethod(currentClass, @selector(resume))) {
//4陪白、獲取當前class的父類颈走,super class
Class superClass = [currentClass superclass];
//5、獲取當前類對resume的實現(xiàn)
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
//6咱士、獲取父類對resume的實現(xiàn)
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
//7立由、如果當前class對于resume的實現(xiàn)和父類不一樣(類似iOS7上的情況)轧钓,并且當前class的resume實現(xiàn)和af_resume不一樣,才進行method swizzling
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
//8锐膜、設置當前操作的class為其父類class毕箍,重復步驟3~8
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
AFURLSessionManager
- (instancetype)initWithSessionConfiguration:
(NSURLSessionConfiguration *)configuration
從初始化函數(shù)中可以看出
1、創(chuàng)建的NSOperationQueue且并發(fā)數(shù)為1
2枣耀、responseSerializer默認為json格式
3霉晕、securityPolicy默認為defaultPolicy
4、reachabilityManager用于監(jiān)控網絡狀態(tài)
- (NSString *)taskDescriptionForSessionTasks {
return [NSString stringWithFormat:@"%p", self];
}
- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}
taskDescriptionForSessionTasks這個方法的作用是之后判斷消息是不是來源于AFN捞奕。AFN在添加每個任務的時候牺堰,都會給task的taskDescription賦值為self.taskDescriptionForSessionTasks。
在上面的taskDidResume可以看到颅围,這里判斷了一下taskDescription是否一致伟葫。
為什么這塊要這么處理?解釋一下:
因為前面對resume函數(shù)進行了swizzling院促,而af_resume里面post了一個名為AFNSURLSessionTaskDidResumeNotification的消息筏养,但是如果用戶采用的不是AFN調用的af_resume,其實也會發(fā)這個消息常拓,所以我們要進行過濾渐溶。
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
這兩個方法是把task和delegate進行關聯(lián)
- (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;
}
給datatask添加delegate,AFN中的每一個task肯定都有一個delegate弄抬。根據這個方法茎辐,我們可以給出task添加代理的步驟:
1、新建AFURLSessionManagerTaskDelegate
2掂恕、設置delegate
3拖陆、設置dataTask.taskDescription
4、task懊亡、delegate依啰、AFURLSessionManager建立聯(lián)系
上面這個函數(shù)中有這么一個操作
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
這么寫是什么意思呢?測試如下:
- (void)testUnion {
NSArray *array1 = [NSArray arrayWithObjects:@1, @2, @1, @3, nil];
NSArray *array2 = [NSArray arrayWithObjects:@3, @1, @3, @5, nil];
NSArray *result1 = [@[array1, array2] valueForKeyPath:@"@distinctUnionOfArrays.self"];
NSLog(@"%@", result1);
NSArray *result2 = [@[array1, array2] valueForKeyPath:@"@unionOfArrays.self"];
NSLog(@"%@", result2);
NSArray *result3 = [@[array1, array2] valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSLog(@"%@", result3);
NSArray *result4 = [@[array1, array2] valueForKeyPath:@"@unionOfObjects.self"];
NSLog(@"%@", result4);
NSArray *result5 = [array1 valueForKeyPath:@"@unionOfObjects.self"];
NSLog(@"%@", result5);
NSArray *result6 = [array1 valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSLog(@"%@", result6);
}
輸出結果:
2018-01-13 21:26:49.351257+0800 test[41247:3156074] (
5,
1,
2,
3
)
2018-01-13 21:26:49.351524+0800 test[41247:3156074] (
1,
2,
1,
3,
3,
1,
3,
5
)
2018-01-13 21:26:49.351806+0800 test[41247:3156074] (
(
1,
2,
1,
3
),
(
3,
1,
3,
5
)
)
2018-01-13 21:26:49.351978+0800 test[41247:3156074] (
(
1,
2,
1,
3
),
(
3,
1,
3,
5
)
)
2018-01-13 21:26:49.352253+0800 test[41247:3156074] (
1,
2,
1,
3
)
2018-01-13 21:26:52.527501+0800 test[41247:3156074] (
3,
2,
1
)
從結果中可以得出如下結論:
1店枣、@distinctUnionOfArrays.self 數(shù)組合并速警,且內部去重
2、@unionOfArrays.self 數(shù)組合并鸯两,不去重
3坏瞄、@distinctUnionOfObjects.self、@unionOfObjects.self應用于數(shù)組時無效甩卓,數(shù)組也不會合并
4、@distinctUnionOfObjects.self 單個數(shù)組去重
5蕉斜、@"@unionOfObjects.self" 不去重
_cmd
當作參數(shù)逾柿。在oc中缀棍,當方法被編譯器轉換成objc_msgSend函數(shù)后,除了方法必須的參數(shù)机错,objc_msgSend還會接收兩個特殊的參數(shù):receiver 與 selector爬范。
objc_msgSend(receiver, selector, arg1, arg2, ...)
receiver 表示當前方法調用的類實例對象。
selector則表示當前方法所對應的selector弱匪。
這兩個參數(shù)是編譯器自動填充的青瀑,我們在調用方法時,不必在源代碼中顯示傳入萧诫,因此可以被看做是“隱式參數(shù)”斥难。
如果想要在source code中獲取這兩個參數(shù),則可以用self(當前類實例對象)和_cmd(當前調用方法的selector)來表示帘饶。
舉個例子:
- (void)testCmd {
NSLog(@"%@ %@", [self class], NSStringFromSelector(_cmd));
}
打印出如下結果:
2018-01-13 21:34:37.574305+0800 test[41300:3165057] ViewController testCmd
NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//創(chuàng)建默認的處理方式哑诊,PerformDefaultHandling方式將忽略Credential這個參數(shù)
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//調動自身的處理方法,也就是說我們通過sessionDidReceiveAuthenticationChallenge這個block接收session及刻,challenge 參數(shù)镀裤,返回一個NSURLSessionAuthChallengeDisposition結果,這個業(yè)務使我們自己在這個block中完成缴饭。
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {//如果沒有實現(xiàn)自定義的驗證過程
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//使用安全策略來驗證
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//如果驗證通過暑劝,根據serverTrust創(chuàng)建依據
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
//有的話就返回UseCredential
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {//沒有驗證通過,返回NSURLSessionAuthChallengeCancelAuthenticationChallenge
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
這個代理方法會在下面兩種情況被調用
1颗搂、當遠程服務器要求客戶端提供證書或者Windows NT LAN Manager (NTLM)驗證
2担猛、當session初次和服務器通過SSL或TSL建立連接,客戶端需要驗證服務端證書鏈
如果沒有實現(xiàn)這個方法峭火,session就會調用delegate的URLSession:task:didReceiveChallenge:completionHandler:方法毁习。
如果challenge.protectionSpace.authenticationMethod 在下邊4個中時,才會調用
* NSURLAuthenticationMethodNTLM
* NSURLAuthenticationMethodNegotiate 是否使用 Kerberos or NTLM 驗證
* NSURLAuthenticationMethodClientCertificate
* NSURLAuthenticationMethodServerTrust
否則調用`URLSession:task:didReceiveChallenge:completionHandler:`方法卖丸。