AFN 3.0學習總結(五)

參考: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
image

通過這個宏定義听怕,我們可以聯(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

image

AFN中所有的和創(chuàng)建任務相關的事件都放到了一個單例隊列中翅睛,而且是串行的声搁。

image

從名字就可以看出,這是一個安全的創(chuàng)建任務的方法

知識點:dispatch_block_t

系統(tǒng)是這么定義的

typedef void (^dispatch_block_t)(void);

關于這個block我們要注意以下幾點:

1捕发、MRC情況下疏旨,使用完要記得release

2、生命block時扎酷,它是被分配到棧上的檐涝,使用時,我們需要把block拷貝到堆上面法挨,因為棧是系統(tǒng)管理的谁榜,隨時可能被釋放。

image

這個方法是用來創(chuàng)建一個隊列來管理數(shù)據的處理凡纳,同上面的方法類似窃植,不過這個創(chuàng)建的是并發(fā)的,加快的數(shù)據處理的速度惫企。

AFURLSessionManagerTaskDelegate

這個代理的目的:

1撕瞧、處理上傳或者下載的進度

2陵叽、處理獲取完數(shù)據后的行為

image

需要說一下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這個屬性來獲取進度

image

移除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)系

image.png

上面這個函數(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" 不去重

image.png
我們注意到上面方法中使用了_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:`方法卖丸。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纺且,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子稍浆,更是在濱河造成了極大的恐慌载碌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衅枫,死亡現(xiàn)場離奇詭異嫁艇,居然都是意外死亡,警方通過查閱死者的電腦和手機弦撩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門步咪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人益楼,你說我怎么就攤上這事猾漫〉闱纾” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵悯周,是天一觀的道長粒督。 經常有香客問我,道長禽翼,這世上最難降的妖魔是什么屠橄? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮闰挡,結果婚禮上锐墙,老公的妹妹穿的比我還像新娘。我一直安慰自己解总,他們只是感情好贮匕,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著花枫,像睡著了一般刻盐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上劳翰,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天敦锌,我揣著相機與錄音,去河邊找鬼佳簸。 笑死乙墙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的生均。 我是一名探鬼主播听想,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼马胧!你這毒婦竟也來了汉买?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤佩脊,失蹤者是張志新(化名)和其女友劉穎蛙粘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體威彰,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡出牧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了歇盼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舔痕。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出赵讯,到底是詐尸還是另有隱情盈咳,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布边翼,位于F島的核電站,受9級特大地震影響鸣剪,放射性物質發(fā)生泄漏组底。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一筐骇、第九天 我趴在偏房一處隱蔽的房頂上張望债鸡。 院中可真熱鬧,春花似錦铛纬、人聲如沸厌均。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棺弊。三九已至,卻和暖如春擒悬,著一層夾襖步出監(jiān)牢的瞬間模她,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工懂牧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侈净,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓僧凤,卻偏偏與公主長得像畜侦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子躯保,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容