本篇是AFNetworking 3.0
源碼解讀的第五篇了。
AFNetworking 3.0 源碼解讀(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源碼解讀(二)之 AFSecurityPolicy
AFNetworking 3.0 源碼解讀(三)之 AFURLRequestSerialization
AFNetworking 3.0 源碼解讀(四)之 AFURLResponseSerialization
這次主要介紹AFURLSessionManager這個類了。下一篇會介紹 AFHTTPSessionManager 。它是AFURLSessionManager的一個子類。
其實,AFURLSessionManager 創建並管理着NSURLSession這個對象。而NSURLSession又基於NSURLSessionConfiguration。
AFURLSessionManager實現了四個協議:
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:
上邊的這些協議方法在實現部分都會介紹到,如果自己寫的子類中重寫了這些代理方法,一定要調用[super xxx]。這篇也會很長,本來打算差分成兩個篇幅的,先因為方法大都比較榮日理解,最終決定還是放在一篇中比較好理解。
我們對AFURLSessionManager的頭文件做一個介紹:
1. @property (readonly, nonatomic, strong) NSURLSession *session;
關於NSURLSession的介紹,可以參考官方的文檔 ,文檔中提出,相對於NSURLConnection ,NSURLSession強大的功能是支持后台上傳和下載。不過值得注意的是,這個對象與它的delegate之間的是一個強引用關系,因此在釋放NSURLSession時,要做好處理。
在網上看到了這篇文章, 使用NSURLSession,可以說大體的講了NSURLSession的用法,不過我更喜歡開頭的那首詩。
有的程序員老了,還沒聽過NSURLSession
有的程序員還嫩,沒用過NSURLConnection
有的程序員很單純,他只知道AFN.
2.@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
為NSURLSession 綁定一個隊列。並且設置這個隊列的最大並發數maxConcurrentOperationCount為1.
3.@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
這是序列化響應數據的對象,默認的模式是AFJSONResponseSerializer,而且不能為空。
4.@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
安全策略,默認是defaultPolicy。
5.@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
網絡監控管理者。
跟獲取會話任務相關的屬性:
6.@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
當前被管理的包括data upload download 的任務的集合
7.@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
當前 data 的任務集合
8.@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
當前 upload 的任務集合
9.@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;
當前 download 的任務集合。
回調的隊列
10.@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
請求成功后,回調block會在這個隊列中調用,如果為空,就在主隊列。
11.@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
請求成功后,回調block會在這個組中調用,如果為空,就使用一個私有的。
修復后台操作的bug
12.@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions;
這個屬性用來解決在后台創建上傳任務返回nil的bug,默認為NO,如果設為YES,在后台創建上傳任務失敗會,會嘗試重新創建該任務。
初始化相關
13.- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
這個方法是指定的初始化方法。那么什么叫指定的呢?
NS_DESIGNATED_INITIALIZER
這個宏告訴開發者,如果寫一個集成A類的子類B,那么就要調用父類A的制定的初始化方法。舉個例子:
@interface MyClass : NSObject
@property(copy, nonatomic) NSString *name;
-(instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
-(instancetype)init;
@end
如果我集成了MyClass而沒有時間initWithName: 方法,就會收到一個警告信息。點擊這里查看詳細信息
14.- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
根據是否取消未完成的任務來是session失效。
NSURLSession有兩個方法:
- -(void)finishTasksAndInvalidate; 標示待完成所有的任務后失效
- -(void)invalidateAndCancel; 標示 立即失效,未完成的任務也將結束
NSURLSessionDataTask
15.- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
16.- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
上邊的這兩個方法是和DataTask 相關的方法。
NSURLSessionUploadTask
17.- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
18.- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(nullable NSData *)bodyData
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
19.- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
上邊的這三個方法是和UploadTask 相關的方法。分別對應fileURL/data/request 這三種不同的數據源。
NSURLSessionDownloadTask
20.- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
21.- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
上邊的這兩個方法是和DownloadTask 相關的方法。
頭文件中剩余的方法就是跟最開始給出的代理有關了,讓我們能夠通過Block來處理那些代理的事件。在這就不做介紹了。
在這里再羅列出使用這個類中用到的通知:
- AFNetworkingTaskDidResumeNotification
- AFNetworkingTaskDidCompleteNotification
- AFNetworkingTaskDidSuspendNotification
- AFURLSessionDidInvalidateNotification
- AFURLSessionDownloadTaskDidFailToMoveFileNotification
- AFNetworkingTaskDidCompleteResponseDataKey
- AFNetworkingTaskDidCompleteSerializedResponseKey
- AFNetworkingTaskDidCompleteResponseSerializerKey
- AFNetworkingTaskDidCompleteAssetPathKey
- AFNetworkingTaskDidCompleteErrorKey
** 通過Block和通知,我們就有能力接收到跟網絡請求先關的事件和數據。也就是我們可以使用這些來處理我們的業務邏輯。**
我們現在來看.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
#endif
上邊的這個宏的目的是通過NSFoundation的版本來判斷當前ios版本,關鍵是這個宏的調試目標是IOS,來看看系統是怎么定義的:
那么我們就能夠聯想到,目前我們能夠判斷系統版本號的方法有幾種呢?最少三種:
- [UIDevice currentDevice].systemVersion
- 通過比較Foundation框架的版本號,iOS系統升級的同時Foundation框架的版本也會提高
- 通過在某系版本中新出現的方法來判斷,UIAlertController 這個類是iOS8之后才出現的 NS_CLASS_AVAILABLE_IOS(8_0),如果當前系統版本沒有這個類NSClassFromString(@"UIAlertController" == (null),從而判斷當前版本是否大於等於iOS8
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
AFNetworking中所有的和創建任務相關的事件都放到了一個單例的隊列中,我們平時可能會使用這些方法,但還是可能會忽略一些內容,dispatch_queue_create()這個是隊列的方法,第一個參數是隊列的identifier,第二個參數則表示這個隊列是串行隊列還是並行隊列。
如果第二個參數為DISPATCH_QUEUE_SERIAL或NULL 則表示隊列為串行隊列。如果為DISPATCH_QUEUE_CONCURRENT則表示是並行隊列。
關於隊列的小的知識點,參考了這篇文章:Objective C 高級進階— GCD隊列淺析(一).
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
再看這個方法,看名字能夠知道這應該是一個安全創建人物的方法,那么我們會很疑惑,為什么創建人物要是安全的呢?難道我們按照順序創建人物,根據各自的Block回調處理事件會有問題?? 是的,按照https://github.com/AFNetworking/AFNetworking/issues/2093這個的描述:
加入我們創建了一個人物task1
對應completionHandler1,然后又創建了task2
對應的completionHandler2,這時候在task2
數據還沒有返回的前提下,task1
的數據返回了,就會調用completionHandler2,就是這樣的一個bug,造成任務的創建是不安全的,不過這個問題已經在ios8后修復了。
這個方法還有一個小知識點:dispatch_block_t
,點擊去可以看到:
typedef void (^dispatch_block_t)(void);
關於這個Block我們應該注意幾點:
- 非ARC情況下,Block被allocated或者copied到堆后,一定要記得釋放它,通過[release]或者Block_release()
- 聲明Block時,它是被分配到棧上的,要使用他,需要copy到堆才安全,因為棧內存是系統管理的,隨時可能被釋放。
static dispatch_queue_t url_session_manager_processing_queue() {
static dispatch_queue_t af_url_session_manager_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_url_session_manager_processing_queue;
}
這個方法是創建一個隊列用來管理數據的處理。和上邊的創建的方法對比,這個方法創建的隊列是一個並行的隊列,這就加快了數據的處理速度。
static dispatch_group_t url_session_manager_completion_group() {
static dispatch_group_t af_url_session_manager_completion_group;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_completion_group = dispatch_group_create();
});
return af_url_session_manager_completion_group;
}
typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task);
大概講一下這個的使用方法,其實這行代碼的目的就是給一個Block定義一個名稱,在AFNEtworking中后邊的代碼,在使用這個Block的時候,就這么使用
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
AFURLSessionManagerTaskDelegate
這個代理對象的目的是:
- 處理上傳或下載的進度
- 處理獲取完數據后的行為
看這些屬性,我們需要了解的是NSProgress這個類,這個類是apple為了管理進度在ios7新增的類。我們在ios開發中,但凡使用到跟進度相關的功能時,應盡量考慮始終它。它內部是使用kvo機制監聽進度的。
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.mutableData = [NSMutableData data];
self.uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
self.uploadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown;
self.downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
self.downloadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown;
return self;
}
來看看如何把task和進度綁定在一起
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask = task;
// 設置進度的總單元數
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
// 設置上傳為可取消的
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
// 設置上傳為可暫停的
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
// 設置重新開始
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.uploadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.downloadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.downloadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
這個方法很長,但是也很簡單,通過task.countOfBytesExpectedToSend能夠獲取到發送數據的總大小,通過task.countOfBytesExpectedToReceive能夠獲取到下載數據的總大小。
NSProgress 通過監聽fractionCompleted這個屬性來獲取進度。
注意:在寫監聽方法的時候,這個options使用了NSKeyValueObservingOptionNew,代表什么意思呢?
點擊去后看到是一個NSKeyValueObservingOptions的枚舉:
- NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法
- NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法
- NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值
- NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之后
--
// 取消監聽
- (void)cleanUpProgressForTask:(NSURLSessionTask *)task {
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))];
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))];
[self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
[self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}
--
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
}
}
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
在這里要說一下關於task四個代理的調用問題。
task一共有4個delegate,只要設置了一個,就代表四個全部設置,有時候一些delegate不會被觸發的原因在於這四種delegate是針對不同的URLSession類型和URLSessionTask類型來進行響應的,也就是說不同的類型只會觸發這些delegate中的一部分,而不是觸發所有的delegate。
舉例說明如下
-
觸發NSURLSessionDataDelegate
//使用函數dataTask來接收數據 -(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];
-
觸發NSURLSessionDownloadDelegate
//使用函數downloadTask來接受數據 -(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];
這兩段代碼的主要區別在於NSURLSessionTask的類型的不同,造成了不同的Delegate被觸發.
#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
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__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;
}
//判斷是否有downloadFileURL
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];
});
});
});
}
#pragma clang diagnostic pop
}
這個方法是獲取數據完成了方法。最終通過self.completionHandler和** [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];**這兩個手段來傳遞數據和事件。
我們主要看看這個通知的userinfo會有那些信息:
- AFNetworkingTaskDidCompleteResponseSerializerKey -> manager.responseSerializer
- AFNetworkingTaskDidCompleteAssetPathKey -> self.downloadFileURL
- AFNetworkingTaskDidCompleteResponseDataKey -> data
- AFNetworkingTaskDidCompleteErrorKey -> error
- AFNetworkingTaskDidCompleteSerializedResponseKey -> responseObject
--
#pragma mark - NSURLSessionDownloadTaskDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
[[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
if (fileManagerError) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
這個方法在下載完成后會調用。之前有一個使用場景,就是視頻邊下載邊播放。要求在視頻在下載完之前拿到正在下載的數據。ASI有一個屬性能夠拿到fileURL,AFNetworking卻沒有這個屬性,現在看來,通過設置
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
可以把數據寫到一個我們定義的臨時的地方
_AFURLSessionTaskSwizzling
當時看這個私有類的時候一直想不通為什么要弄一個這樣的類呢?首先看了AFNetworking給出的解釋https://github.com/AFNetworking/AFNetworking/pull/2702 大概說了當初這個私有類的由來,ios7和ios8 task的父類並不一樣,關鍵是resume
and suspend
這兩個方法的調用。
因此,AFNetworking 利用Runtime交換了resume
and suspend
的方法實現。在替換的方法中發送了狀態的通知。這個通知被使用在UIActivityIndicatorView+AFNetworking
這個UIActivityIndicatorView的分類中。
方法的核心部分作用是層級遍歷父類,替換resume
and suspend
的實現方法。同時也解決了鎖死這個bug。
還有值得說的是 + (void)load
這個方法,這個方法會在app啟動時加載所有類的時候調用,且只會調用一次
,所以這就有了使用場景了,當想使用運行時做一些事情的時候,就能夠用上這個方法了。
舉幾個使用這個方法的例子:
下邊就看看代碼部分:
// 根據兩個方法名稱交換兩個方法,內部實現是先根據函數名獲取到對應方法實現
// 再調用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,對應實現為method的方法
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
// 內部實現使用的是class_addMethod方法,注意method_getTypeEncoding是為了獲得該方法的參數和返回類型
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添加一個名為af_resume的方法,使用@selector(af_resume)獲取方法名,使用afResumeMethod作為方法實現
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
// 交換resume和af_resume的方法實現
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
// 同上
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
--
- (NSURLSessionTaskState)state {
NSAssert(NO, @"State method should never be called in the actual dummy class");
// 初始狀態是NSURLSessionTaskStateCanceling;
return NSURLSessionTaskStateCanceling;
}
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume]; // 因為經過method swizzling后,此處的af_resume其實就是之前的resume,所以此處調用af_resume就是調用系統的resume。但是在程序中我們還是得使用resume,因為其實際調用的是af_resume
// 如果之前是其他狀態,就變回resume狀態,此處會通知調用taskDidResume
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: 高能預警
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
// 擔心以后iOS中不存在NSURLSessionTask
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7和iOS 8在NSURLSessionTask實現上有些許不同,這使得下面的代碼實現略顯trick
關於這個問題,大家做了很多Unit Test,足以證明這個方法是可行的
目前我們所知的:
- NSURLSessionTasks是一組class的統稱,如果你僅僅使用提供的API來獲取NSURLSessionTask的class,並不一定返回的是你想要的那個(獲取NSURLSessionTask的class目的是為了獲取其resume方法)
- 簡單地使用[NSURLSessionTask class]並不起作用。你需要新建一個NSURLSession,並根據創建的session再構建出一個NSURLSessionTask對象才行。
- iOS 7上,localDataTask(下面代碼構造出的NSURLSessionDataTask類型的變量,為了獲取對應Class)的類型是 __NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask。
- iOS 8上,localDataTask的類型為__NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自NSURLSessionTask
- iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現了resume和suspend方法的類,另外__NSCFLocalSessionTask中的resume和suspend並沒有調用其父類(即__NSCFURLSessionTask)方法,這也意味着兩個類的方法都需要進行method swizzling。
- iOS 8上,NSURLSessionTask是唯一實現了resume和suspend方法的類。這也意味着其是唯一需要進行method swizzling的類
- 因為NSURLSessionTask並不是在每個iOS版本中都存在,所以把這些放在此處(即load函數中),比如給一個dummy class添加swizzled方法都會變得很方便,管理起來也方便。
一些假設前提:
- 目前iOS中resume和suspend的方法實現中並沒有調用對應的父類方法。如果日后iOS改變了這種做法,我們還需要重新處理
- 沒有哪個后台task會重寫resume和suspend函數
*/
// 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) 檢查當前class是否實現了resume。如果實現了,繼續第4步。
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4) 獲取到當前class的父類(superClass)
Class superClass = [currentClass superclass];
// 5) 獲取到當前class對於resume實現的指針
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
// 6) 獲取到父類對於resume實現的指針
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
// 7) 如果當前class對於resume的實現和父類不一樣(類似iOS7上的情況),並且當前class的resume實現和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)init {
return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
可以看出默認創建一個NSOperationQueue且並發數為一個,默認的responseSerializer響應序列化為Json,默認的securityPolicy為defaultPolicy,同時添加reachabilityManager網絡監控對象。
- (NSString *)taskDescriptionForSessionTasks {
return [NSString stringWithFormat:@"%p", self];
}
這個方法返回一個本類的地址,目的是通過這個字符串來判斷請求是不是來源於AFNetworking。AFNetworking 在為每個task添加Delegate的時候,都會給task的taskDescription賦值為self.taskDescriptionForSessionTasks
。在后邊的- (NSArray *)tasksForKeyPath:(NSString *)keyPath
方法中會使用到這個字符串。
- (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];
});
}
}
}
- (void)taskDidSuspend:(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:AFNetworkingTaskDidSuspendNotification object:task];
});
}
}
}
這兩個是通知方法,來源於下邊的兩個通知的監聽事件:
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
- (void)removeNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidSuspendNotification object:task];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidResumeNotification object:task];
}
還記得上邊提到的_AFURLSessionTaskSwizzling這個私有類嗎?它交換了resume
and suspend
這兩個方法,在方法中發了下邊兩個通知:
- AFNSURLSessionTaskDidResumeNotification
- AFNSURLSessionTaskDidSuspendNotification
接下來就是一個很巧妙的轉化過程了,按理說我們只需要接受並處理上邊的兩個通知不就可以了嗎? 但真實情況卻不是這樣的,並不是所有人使用網絡請求都是用AFNetworking,所以使用if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks])
來做判斷,這個task是否來自AFNetworking。
轉化后我們就是用下邊的通知,同時也是對外暴露出來的通知:
- AFNetworkingTaskDidResumeNotification
- AFNetworkingTaskDidSuspendNotification
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
這兩個方法是把AFURLSessionManagerTaskDelegate
和task
建立聯系。值得注意的是:
- self.mutableTaskDelegatesKeyedByTaskIdentifier 這個字典以task.taskIdentifier為key,delegate為value。同事在讀取和設置的時候采用加鎖來保證安全。
- 在給task添加delegate的時候除了給self.mutableTaskDelegatesKeyedByTaskIdentifier賦值外,還需要設置delegate的ProgressForTask,且添加task的通知。
--
- (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] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
給datatask添加delegate,AFNetworking中的每一個task肯定都有一個delegate。根據這個方法,我們可以看出給task添加代理的步驟為:
- 新建AFURLSessionManagerTaskDelegate
- 設置delegate
- 設置taskDescription
- 把
task
delegate
AFURLSessionManager
建立聯系
--
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
[self.lock lock];
[delegate cleanUpProgressForTask:task];
[self removeNotificationObserverForTask:task];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
getTasksWithCompletionHandler 這個方法是異步方法,上邊的方法中我們需要等待這個異步方法有結果后才能進行后邊的代碼。 我們就可以使用dispatch_semaphore_t 這個信號來實現異步等待。
具體過程如下:
- 新建一個信號
- 在異步方法中發送信號,也就說一旦我們得到了異步的結果,我們就發一個信號
- 等待信號,只有接收到指定的信號代碼才會往下走
這個信號的使用場景有很多,可以當安全鎖來使用,也可以像上邊一樣異步等待。 假如我們有這樣一個場景:我們有3個或者多個異步的網絡請求,必須等待所有的請求回來后,在使用這些請求的結果來做一些事情。那么該怎么辦呢? 解決方案就是:使用dispatch_group_t 和 dispatch_semaphore_t來實現。 在這里代碼就不貼出來了,有興趣的朋友而已自己google或者留言。
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
這么使用之前確實不太知道,如果是我,可能就直接賦值給數組了。那么@unionOfArrays.self
又是什么意思呢?
- @distinctUnionOfObjects 清楚重復值
- unionOfObjects 保留重復值
--
- (NSArray *)tasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
- (NSArray *)dataTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
- (NSArray *)uploadTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
- (NSArray *)downloadTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
在oc中,當方法被編譯器轉換成objc_msgSend函數后,除了方法必須的參數,objc_msgSend還會接收兩個特殊的參數:receiver 與 selector。
objc_msgSend(receiver, selector, arg1, arg2, ...)
receiver 表示當前方法調用的類實例對象。
selector則表示當前方法所對應的selector。
這兩個參數是編譯器自動填充的,我們在調用方法時,不必在源代碼中顯示傳入,因此可以被看做是“隱式參數”。
如果想要在source code中獲取這兩個參數,則可以用self(當前類實例對象)和_cmd(當前調用方法的selector)來表示。
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"Current method: %@ %@",[self class],NSStringFromSelector(_cmd));
}
輸出結果為:
TestingProject[570:11303] Current method: FirstViewController viewDidLoad
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
這里大概說下幾種比較典型的創建task的方法,其他的方法就不做介紹了,原理大體相同。分為下邊兩個步驟:
- 創建task
- 給task添加Delegate
--
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
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 fromFile:fileURL];
});
// 當uploadtTask創建失敗,且允許自動創建,會嘗試創建uploadtTask
if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) {
for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) {
uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
}
}
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
return uploadTask;
}
--
- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task {
return [[self delegateForTask:task] uploadProgress];
}
- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task {
return [[self delegateForTask:task] downloadProgress];
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, session: %@, operationQueue: %@>", NSStringFromClass([self class]), self, self.session, self.operationQueue];
}
假如我們自己寫了一個工具類,我們最好重寫description方法。
- (BOOL)respondsToSelector:(SEL)selector {
if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
return self.taskWillPerformHTTPRedirection != nil;
} else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
return self.dataTaskDidReceiveResponse != nil;
} else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
return self.dataTaskWillCacheResponse != nil;
} else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
return self.didFinishEventsForBackgroundURLSession != nil;
}
return [[self class] instancesRespondToSelector:selector];
}
我們也可以使用respondsToSelector這個方法來攔截事件,把系統的事件和自定義的事件進行綁定。
NSURLSessionDelegate
// 這個方法是session收到的最后一條信息,
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
// 調用block
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
// 發送通知
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
--
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
// 創建默認的處理方式,PerformDefaultHandling方式將忽略credential這個參數
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 調動自身的處理方法,也就是說我們通過sessionDidReceiveAuthenticationChallenge這個block接收session,challenge 參數,返回一個NSURLSessionAuthChallengeDisposition結果,這個業務使我們自己在這個block中完成。
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
}
// 如果沒有實現自定義的驗證過程
else {
// 判斷challenge的authenticationMethod
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 使用安全策略來驗證
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 如果驗證通過,根據serverTrust創建依據
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) { // 有的話就返回UseCredential
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else { // 驗證沒通過,返回CancelAuthenticationChallenge
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
着重對這個方法介紹下。
這個代理方法會在下邊兩種情況下被調用:
- 當遠程服務器要求客戶端提供證書或者Windows NT LAN Manager (NTLM)驗證
- 當session初次和服務器通過SSL或TSL建立連接,客戶端需要驗證服務端證書鏈
如果沒有實現這個方法,session就會調用delegate的URLSession:task:didReceiveChallenge:completionHandler:
方法。
如果challenge.protectionSpace.authenticationMethod 在下邊4個中時,才會調用
- NSURLAuthenticationMethodNTLM
- NSURLAuthenticationMethodNegotiate 是否使用 Kerberos or NTLM 驗證
- NSURLAuthenticationMethodClientCertificate
- NSURLAuthenticationMethodServerTrust
否則調用
URLSession:task:didReceiveChallenge:completionHandler:
方法。
NSURLSessionTaskDelegate
// 請求改變的時候調用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSURLRequest *redirectRequest = request;
if (self.taskWillPerformHTTPRedirection) {
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
}
if (completionHandler) {
completionHandler(redirectRequest);
}
}
// 使用方法同 URLSession: didReceiveChallenge: completionHandler: 差不多
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
// 請求需要一個全新的,未打開的數據時調用。特別是請求一個body失敗時,可以通過這個方法給一個新的body
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
NSInputStream *inputStream = nil;
if (self.taskNeedNewBodyStream) {
inputStream = self.taskNeedNewBodyStream(session, task);
} else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
inputStream = [task.originalRequest.HTTPBodyStream copy];
}
if (completionHandler) {
completionHandler(inputStream);
}
}
// 上傳數據時候調用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
int64_t totalUnitCount = totalBytesExpectedToSend;
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
if(contentLength) {
totalUnitCount = (int64_t) [contentLength longLongValue];
}
}
if (self.taskDidSendBodyData) {
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
}
}
// 完成時調用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
NSURLSessionDataDelegate
// 收到響應時調用
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
if (completionHandler) {
completionHandler(disposition);
}
}
// 當NSURLSessionDataTask變為NSURLSessionDownloadTask調用,之后NSURLSessionDataTask將不再接受消息
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
[self removeDelegateForTask:dataTask];
// 重新設置代理
[self setDelegate:delegate forTask:downloadTask];
}
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
// 接受數據過程中,調用,只限於NSURLSessionDataTask
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
// 即將緩存響應時調用
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.dataTaskWillCacheResponse) {
cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
// 后台任務完成成后
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
NSURLSessionDownloadDelegate
// 下載完成后調用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
if (self.downloadTaskDidFinishDownloading) {
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (fileURL) {
delegate.downloadFileURL = fileURL;
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
if (error) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
return;
}
}
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
// 下載中調用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (self.downloadTaskDidWriteData) {
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
// 回復下載時調用,使用fileOffset實現
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
if (self.downloadTaskDidResume) {
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
}
}
** 好了,這篇文章就到此為之了,到目前位置,AFNetworking已經解讀了5篇了,所有的核心類也解釋完畢,下一篇文章會是AFHTTPSessionManager這個類了 。我們最終的目標是寫一個通用的包含大部分功能的網絡框架,這個需要在解讀完剩余的類之后再實現。我會演示一個從無到有的網絡框架的產生過程。**