本篇是AFNetworking 3.0
源碼解讀的第五篇了羡洛。
AFNetworking 3.0 源碼解讀(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源碼解讀(二)之 AFSecurityPolicy
AFNetworking 3.0 源碼解讀(三)之 AFURLRequestSerialization
AFNetworking 3.0 源碼解讀(四)之 AFURLResponseSerialization
這次主要介紹AFURLSessionManager這個類了瘦棋。下一篇會介紹 AFHTTPSessionManager 本辐。它是AFURLSessionManager的一個子類。
其實嘹黔,AFURLSessionManager 創(chuàng)建并管理著NSURLSession這個對象峭状。而NSURLSession又基于NSURLSessionConfiguration。
AFURLSessionManager實現(xiàn)了四個協(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:
上邊的這些協(xié)議方法在實現(xiàn)部分都會介紹到泊窘,如果自己寫的子類中重寫了這些代理方法熄驼,一定要調(diào)用[super xxx]。這篇也會很長烘豹,本來打算差分成兩個篇幅的瓜贾,先因為方法大都比較榮日理解,最終決定還是放在一篇中比較好理解携悯。
我們對AFURLSessionManager的頭文件做一個介紹:
1. @property (readonly, nonatomic, strong) NSURLSession *session;
關(guān)于NSURLSession的介紹祭芦,可以參考官方的文檔 ,文檔中提出憔鬼,相對于NSURLConnection 龟劲,NSURLSession強大的功能是支持后臺上傳和下載。不過值得注意的是轴或,這個對象與它的delegate之間的是一個強引用關(guān)系昌跌,因此在釋放NSURLSession時,要做好處理照雁。
在網(wǎng)上看到了這篇文章蚕愤, 使用NSURLSession,可以說大體的講了NSURLSession的用法,不過我更喜歡開頭的那首詩萍诱。
有的程序員老了,還沒聽過NSURLSession
有的程序員還嫩,沒用過NSURLConnection
有的程序員很單純,他只知道AFN.
2.@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
為NSURLSession 綁定一個隊列悬嗓。并且設(shè)置這個隊列的最大并發(fā)數(shù)maxConcurrentOperationCount為1.
3.@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
這是序列化響應(yīng)數(shù)據(jù)的對象,默認的模式是AFJSONResponseSerializer裕坊,而且不能為空包竹。
4.@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
安全策略,默認是defaultPolicy籍凝。
5.@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
網(wǎng)絡(luò)監(jiān)控管理者映企。
跟獲取會話任務(wù)相關(guān)的屬性:
6.@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
當前被管理的包括data upload download 的任務(wù)的集合
7.@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
當前 data 的任務(wù)集合
8.@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
當前 upload 的任務(wù)集合
9.@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;
當前 download 的任務(wù)集合。
回調(diào)的隊列
10.@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
請求成功后静浴,回調(diào)block會在這個隊列中調(diào)用堰氓,如果為空,就在主隊列苹享。
11.@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
請求成功后双絮,回調(diào)block會在這個組中調(diào)用,如果為空得问,就使用一個私有的囤攀。
修復(fù)后臺操作的bug
12.@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions;
這個屬性用來解決在后臺創(chuàng)建上傳任務(wù)返回nil的bug,默認為NO宫纬,如果設(shè)為YES焚挠,在后臺創(chuàng)建上傳任務(wù)失敗會,會嘗試重新創(chuàng)建該任務(wù)漓骚。
初始化相關(guān)
13.- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
這個方法是指定的初始化方法蝌衔。那么什么叫指定的呢?
NS_DESIGNATED_INITIALIZER
這個宏告訴開發(fā)者蝌蹂,如果寫一個集成A類的子類B噩斟,那么就要調(diào)用父類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;
根據(jù)是否取消未完成的任務(wù)來是session失效。
NSURLSession有兩個方法:
- -(void)finishTasksAndInvalidate; 標示待完成所有的任務(wù)后失效
- -(void)invalidateAndCancel; 標示 立即失效齐鲤,未完成的任務(wù)也將結(jié)束
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 相關(guān)的方法斥废。
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 相關(guān)的方法。分別對應(yīng)fileURL/data/request 這三種不同的數(shù)據(jù)源给郊。
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 相關(guān)的方法牡肉。
頭文件中剩余的方法就是跟最開始給出的代理有關(guān)了,讓我們能夠通過Block來處理那些代理的事件丑罪。在這就不做介紹了荚板。
在這里再羅列出使用這個類中用到的通知:
- AFNetworkingTaskDidResumeNotification
- AFNetworkingTaskDidCompleteNotification
- AFNetworkingTaskDidSuspendNotification
- AFURLSessionDidInvalidateNotification
- AFURLSessionDownloadTaskDidFailToMoveFileNotification
- AFNetworkingTaskDidCompleteResponseDataKey
- AFNetworkingTaskDidCompleteSerializedResponseKey
- AFNetworkingTaskDidCompleteResponseSerializerKey
- AFNetworkingTaskDidCompleteAssetPathKey
- AFNetworkingTaskDidCompleteErrorKey
** 通過Block和通知凤壁,我們就有能力接收到跟網(wǎng)絡(luò)請求先關(guān)的事件和數(shù)據(jù)吩屹。也就是我們可以使用這些來處理我們的業(yè)務(wù)邏輯跪另。**
我們現(xiàn)在來看.m文件的內(nèi)容
#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版本,關(guān)鍵是這個宏的調(diào)試目標是IOS煤搜,來看看系統(tǒng)是怎么定義的:
那么我們就能夠聯(lián)想到免绿,目前我們能夠判斷系統(tǒng)版本號的方法有幾種呢?最少三種:
- [UIDevice currentDevice].systemVersion
- 通過比較Foundation框架的版本號擦盾,iOS系統(tǒng)升級的同時Foundation框架的版本也會提高
- 通過在某系版本中新出現(xiàn)的方法來判斷嘲驾,UIAlertController 這個類是iOS8之后才出現(xiàn)的 NS_CLASS_AVAILABLE_IOS(8_0),如果當前系統(tǒng)版本沒有這個類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中所有的和創(chuàng)建任務(wù)相關(guān)的事件都放到了一個單例的隊列中,我們平時可能會使用這些方法迹卢,但還是可能會忽略一些內(nèi)容辽故,dispatch_queue_create()這個是隊列的方法,第一個參數(shù)是隊列的identifier腐碱,第二個參數(shù)則表示這個隊列是串行隊列還是并行隊列誊垢。
如果第二個參數(shù)為DISPATCH_QUEUE_SERIAL或NULL 則表示隊列為串行隊列。如果為DISPATCH_QUEUE_CONCURRENT則表示是并行隊列症见。
關(guān)于隊列的小的知識點喂走,參考了這篇文章: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();
}
}
再看這個方法,看名字能夠知道這應(yīng)該是一個安全創(chuàng)建人物的方法谋作,那么我們會很疑惑芋肠,為什么創(chuàng)建人物要是安全的呢?難道我們按照順序創(chuàng)建人物遵蚜,根據(jù)各自的Block回調(diào)處理事件會有問題帖池?? 是的吭净,按照https://github.com/AFNetworking/AFNetworking/issues/2093這個的描述:
加入我們創(chuàng)建了一個人物
task1
對應(yīng)completionHandler1碘裕,然后又創(chuàng)建了task2
對應(yīng)的completionHandler2,這時候在task2
數(shù)據(jù)還沒有返回的前提下攒钳,task1
的數(shù)據(jù)返回了帮孔,就會調(diào)用completionHandler2,就是這樣的一個bug不撑,造成任務(wù)的創(chuàng)建是不安全的文兢,不過這個問題已經(jīng)在ios8后修復(fù)了。
這個方法還有一個小知識點:dispatch_block_t
,點擊去可以看到:
typedef void (^dispatch_block_t)(void);
關(guān)于這個Block我們應(yīng)該注意幾點:
- 非ARC情況下焕檬,Block被allocated或者copied到堆后姆坚,一定要記得釋放它,通過[release]或者Block_release()
-
聲明Block時实愚,它是被分配到棧上的兼呵,要使用他兔辅,需要copy到堆才安全,因為棧內(nèi)存是系統(tǒng)管理的击喂,隨時可能被釋放维苔。
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;
}
這個方法是創(chuàng)建一個隊列用來管理數(shù)據(jù)的處理。和上邊的創(chuàng)建的方法對比懂昂,這個方法創(chuàng)建的隊列是一個并行的隊列介时,這就加快了數(shù)據(jù)的處理速度。
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
這個代理對象的目的是:
- 處理上傳或下載的進度
- 處理獲取完數(shù)據(jù)后的行為
看這些屬性褐澎,我們需要了解的是NSProgress這個類,這個類是apple為了管理進度在ios7新增的類伐蒋。我們在ios開發(fā)中工三,但凡使用到跟進度相關(guān)的功能時,應(yīng)盡量考慮始終它咽弦。它內(nèi)部是使用kvo機制監(jiān)聽進度的徒蟆。
- (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;
// 設(shè)置進度的總單元數(shù)
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
// 設(shè)置上傳為可取消的
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
// 設(shè)置上傳為可暫停的
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
// 設(shè)置重新開始
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能夠獲取到發(fā)送數(shù)據(jù)的總大小段审,通過task.countOfBytesExpectedToReceive能夠獲取到下載數(shù)據(jù)的總大小。
NSProgress 通過監(jiān)聽fractionCompleted這個屬性來獲取進度闹蒜。
注意:在寫監(jiān)聽方法的時候寺枉,這個options使用了NSKeyValueObservingOptionNew,代表什么意思呢绷落?
點擊去后看到是一個NSKeyValueObservingOptions的枚舉:
- NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法
- NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法
- NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法姥闪,一旦注冊,立馬就會調(diào)用一次砌烁。通常它會帶有新值筐喳,而不會帶有舊值
- NSKeyValueObservingOptionPrior 分2次調(diào)用。在值改變之前和值改變之后
--
// 取消監(jiān)聽
- (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);
}
}
}
在這里要說一下關(guān)于task四個代理的調(diào)用問題函喉。
task一共有4個delegate避归,只要設(shè)置了一個,就代表四個全部設(shè)置管呵,有時候一些delegate不會被觸發(fā)的原因在于這四種delegate是針對不同的URLSession類型和URLSessionTask類型來進行響應(yīng)的梳毙,也就是說不同的類型只會觸發(fā)這些delegate中的一部分,而不是觸發(fā)所有的delegate捐下。
舉例說明如下
-
觸發(fā)NSURLSessionDataDelegate
//使用函數(shù)dataTask來接收數(shù)據(jù) -(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];
-
觸發(fā)NSURLSessionDownloadDelegate
//使用函數(shù)downloadTask來接受數(shù)據(jù) -(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
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
// 使用字典來存放請求的結(jié)果
__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);
}
// 發(fā)送通知
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;
// 解析數(shù)據(jù)
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
}
這個方法是獲取數(shù)據(jù)完成了方法萌业。最終通過self.completionHandler和** [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];**這兩個手段來傳遞數(shù)據(jù)和事件。
我們主要看看這個通知的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];
}
}
}
}
<font color='orange'>這個方法在下載完成后會調(diào)用奸柬。之前有一個使用場景生年,就是視頻邊下載邊播放。要求在視頻在下載完之前拿到正在下載的數(shù)據(jù)鸟缕。ASI有一個屬性能夠拿到fileURL晶框,AFNetworking卻沒有這個屬性排抬,現(xiàn)在看來,通過設(shè)置
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
可以把數(shù)據(jù)寫到一個我們定義的臨時的地方</font>
_AFURLSessionTaskSwizzling
當時看這個私有類的時候一直想不通為什么要弄一個這樣的類呢懂从?首先看了AFNetworking給出的解釋https://github.com/AFNetworking/AFNetworking/pull/2702 大概說了當初這個私有類的由來,ios7和ios8 task的父類并不一樣蹲蒲,關(guān)鍵是resume
and suspend
這兩個方法的調(diào)用番甩。
因此,AFNetworking 利用Runtime交換了resume
and suspend
的方法實現(xiàn)届搁。在替換的方法中發(fā)送了狀態(tài)的通知缘薛。這個通知被使用在UIActivityIndicatorView+AFNetworking
這個UIActivityIndicatorView的分類中。
方法的核心部分作用是層級遍歷父類卡睦,替換resume
and suspend
的實現(xiàn)方法宴胧。同時也解決了鎖死這個bug。
還有值得說的是 + (void)load
這個方法表锻,這個方法會在app啟動時加載所有類的時候調(diào)用恕齐,且只會調(diào)用一次
,所以這就有了使用場景了,當想使用運行時做一些事情的時候瞬逊,就能夠用上這個方法了显歧。
舉幾個使用這個方法的例子:
下邊就看看代碼部分:
// 根據(jù)兩個方法名稱交換兩個方法,內(nèi)部實現(xiàn)是先根據(jù)函數(shù)名獲取到對應(yīng)方法實現(xiàn)
// 再調(diào)用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确镊,對應(yīng)實現(xiàn)為method的方法
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
// 內(nèi)部實現(xiàn)使用的是class_addMethod方法士骤,注意method_getTypeEncoding是為了獲得該方法的參數(shù)和返回類型
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作為方法實現(xiàn)
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
// 交換resume和af_resume的方法實現(xiàn)
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");
// 初始狀態(tài)是NSURLSessionTaskStateCanceling;
return NSURLSessionTaskStateCanceling;
}
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume]; // 因為經(jīng)過method swizzling后简逮,此處的af_resume其實就是之前的resume旨巷,所以此處調(diào)用af_resume就是調(diào)用系統(tǒng)的resume巨缘。但是在程序中我們還是得使用resume,因為其實際調(diào)用的是af_resume
// 如果之前是其他狀態(tài)契沫,就變回resume狀態(tài)带猴,此處會通知調(diào)用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: 高能預(yù)警
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
// 擔心以后iOS中不存在NSURLSessionTask
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7和iOS 8在NSURLSessionTask實現(xiàn)上有些許不同,這使得下面的代碼實現(xiàn)略顯trick
關(guān)于這個問題懈万,大家做了很多Unit Test拴清,足以證明這個方法是可行的
目前我們所知的:
- NSURLSessionTasks是一組class的統(tǒng)稱靶病,如果你僅僅使用提供的API來獲取NSURLSessionTask的class,并不一定返回的是你想要的那個(獲取NSURLSessionTask的class目的是為了獲取其resume方法)
- 簡單地使用[NSURLSessionTask class]并不起作用口予。你需要新建一個NSURLSession娄周,并根據(jù)創(chuàng)建的session再構(gòu)建出一個NSURLSessionTask對象才行。
- iOS 7上沪停,localDataTask(下面代碼構(gòu)造出的NSURLSessionDataTask類型的變量煤辨,為了獲取對應(yīng)Class)的類型是 __NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask木张,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask众辨。
- iOS 8上,localDataTask的類型為__NSCFLocalDataTask舷礼,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask鹃彻,__NSCFLocalSessionTask繼承自NSURLSessionTask
- iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現(xiàn)了resume和suspend方法的類妻献,另外__NSCFLocalSessionTask中的resume和suspend并沒有調(diào)用其父類(即__NSCFURLSessionTask)方法蛛株,這也意味著兩個類的方法都需要進行method swizzling。
- iOS 8上育拨,NSURLSessionTask是唯一實現(xiàn)了resume和suspend方法的類谨履。這也意味著其是唯一需要進行method swizzling的類
- 因為NSURLSessionTask并不是在每個iOS版本中都存在,所以把這些放在此處(即load函數(shù)中)熬丧,比如給一個dummy class添加swizzled方法都會變得很方便笋粟,管理起來也方便。
一些假設(shè)前提:
- 目前iOS中resume和suspend的方法實現(xiàn)中并沒有調(diào)用對應(yīng)的父類方法锹引。如果日后iOS改變了這種做法矗钟,我們還需要重新處理
- 沒有哪個后臺task會重寫resume和suspend函數(shù)
*/
// 1) 首先構(gòu)建一個NSURLSession對象session,再通過session構(gòu)建出一個_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實現(xiàn)的指針
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
// 3) 檢查當前class是否實現(xiàn)了resume嫌变。如果實現(xiàn)了吨艇,繼續(xù)第4步。
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4) 獲取到當前class的父類(superClass)
Class superClass = [currentClass superclass];
// 5) 獲取到當前class對于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) 設(shè)置當前操作的class為其父類class倘待,重復(fù)步驟3~8
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
AFURLSessionManager
這個類的屬性我們就不解釋了疮跑,代碼也不貼上來了。我們來看看初始化方法中都設(shè)置了那些默認的值:
- (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];
}
可以看出默認創(chuàng)建一個NSOperationQueue且并發(fā)數(shù)為一個凸舵,默認的responseSerializer響應(yīng)序列化為Json祖娘,默認的securityPolicy為defaultPolicy,同時添加reachabilityManager網(wǎng)絡(luò)監(jiān)控對象啊奄。
- (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];
});
}
}
}
這兩個是通知方法,來源于下邊的兩個通知的監(jiān)聽事件:
- (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
這兩個方法薯鼠,在方法中發(fā)了下邊兩個通知:
- AFNSURLSessionTaskDidResumeNotification
- AFNSURLSessionTaskDidSuspendNotification
接下來就是一個很巧妙的轉(zhuǎn)化過程了,按理說我們只需要接受并處理上邊的兩個通知不就可以了嗎械蹋? 但真實情況卻不是這樣的出皇,<font color=orange>并不是所有人使用網(wǎng)絡(luò)請求都是用AFNetworking</font>,所以使用if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks])
來做判斷朝蜘,這個task是否來自AFNetworking恶迈。
轉(zhuǎn)化后我們就是用下邊的通知涩金,同時也是對外暴露出來的通知:
- 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
建立聯(lián)系谱醇。值得注意的是:
- self.mutableTaskDelegatesKeyedByTaskIdentifier 這個字典以task.taskIdentifier為key,delegate為value步做。同事在讀取和設(shè)置的時候采用加鎖來保證安全副渴。
- 在給task添加delegate的時候除了給self.mutableTaskDelegatesKeyedByTaskIdentifier賦值外,還需要設(shè)置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将鸵。根據(jù)這個方法勉盅,我們可以看出給task添加代理的步驟為:
-
新建AFURLSessionManagerTaskDelegate
-
設(shè)置delegate
-
設(shè)置taskDescription
- 把
task
delegate
AFURLSessionManager
建立聯(lián)系
--
- (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 這個方法是異步方法,上邊的方法中我們需要等待這個異步方法有結(jié)果后才能進行后邊的代碼顶掉。 我們就可以使用dispatch_semaphore_t 這個信號來實現(xiàn)異步等待草娜。
具體過程如下:
-
新建一個信號
-
在異步方法中發(fā)送信號,也就說一旦我們得到了異步的結(jié)果痒筒,我們就發(fā)一個信號
-
等待信號宰闰,只有接收到指定的信號代碼才會往下走
<font color=orange>這個信號的使用場景有很多,可以當安全鎖來使用簿透,也可以像上邊一樣異步等待移袍。 假如我們有這樣一個場景:我們有3個或者多個異步的網(wǎng)絡(luò)請求,必須等待所有的請求回來后老充,在使用這些請求的結(jié)果來做一些事情葡盗。那么該怎么辦呢? 解決方案就是:使用dispatch_group_t 和 dispatch_semaphore_t來實現(xiàn)啡浊。 在這里代碼就不貼出來了觅够,有興趣的朋友而已自己google或者留言路狮。</font>
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
這么使用之前確實不太知道,如果是我蔚约,可能就直接賦值給數(shù)組了奄妨。那么@unionOfArrays.self
又是什么意思呢?
- @distinctUnionOfObjects 清楚重復(fù)值
- unionOfObjects 保留重復(fù)值
--
- (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中苹祟,當方法被編譯器轉(zhuǎn)換成objc_msgSend函數(shù)后砸抛,除了方法必須的參數(shù),objc_msgSend還會接收兩個特殊的參數(shù):receiver 與 selector树枫。
objc_msgSend(receiver, selector, arg1, arg2, ...)
receiver 表示當前方法調(diào)用的類實例對象直焙。
selector則表示當前方法所對應(yīng)的selector。
這兩個參數(shù)是編譯器自動填充的砂轻,我們在調(diào)用方法時奔誓,不必在源代碼中顯示傳入,因此可以被看做是“隱式參數(shù)”搔涝。
如果想要在source code中獲取這兩個參數(shù)厨喂,則可以用self(當前類實例對象)和_cmd(當前調(diào)用方法的selector)來表示。
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"Current method: %@ %@",[self class],NSStringFromSelector(_cmd));
}
輸出結(jié)果為:
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;
}
這里大概說下幾種比較典型的創(chuàng)建task的方法庄呈,其他的方法就不做介紹了蜕煌,原理大體相同。分為下邊兩個步驟:
- 創(chuàng)建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創(chuàng)建失敗诬留,且允許自動創(chuàng)建斜纪,會嘗試創(chuàng)建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這個方法來攔截事件盒刚,把系統(tǒng)的事件和自定義的事件進行綁定。
NSURLSessionDelegate
// 這個方法是session收到的最后一條信息绿贞,
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
// 調(diào)用block
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
// 發(fā)送通知
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
--
- (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;
// 調(diào)動自身的處理方法,也就是說我們通過sessionDidReceiveAuthenticationChallenge這個block接收session樟蠕,challenge 參數(shù)贮聂,返回一個NSURLSessionAuthChallengeDisposition結(jié)果,這個業(yè)務(wù)使我們自己在這個block中完成寨辩。
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
}
// 如果沒有實現(xiàn)自定義的驗證過程
else {
// 判斷challenge的authenticationMethod
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 使用安全策略來驗證
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 如果驗證通過吓懈,根據(jù)serverTrust創(chuàng)建依據(jù)
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);
}
}
著重對這個方法介紹下靡狞。
這個代理方法會在下邊兩種情況下被調(diào)用:
- 當遠程服務(wù)器要求客戶端提供證書或者Windows NT LAN Manager (NTLM)驗證
- 當session初次和服務(wù)器通過SSL或TSL建立連接耻警,客戶端需要驗證服務(wù)端證書鏈
如果沒有實現(xiàn)這個方法,session就會調(diào)用delegate的URLSession:task:didReceiveChallenge:completionHandler:
方法。
如果challenge.protectionSpace.authenticationMethod 在下邊4個中時甘穿,才會調(diào)用
- NSURLAuthenticationMethodNTLM
- NSURLAuthenticationMethodNegotiate 是否使用 Kerberos or NTLM 驗證
- NSURLAuthenticationMethodClientCertificate
- NSURLAuthenticationMethodServerTrust
否則調(diào)用
URLSession:task:didReceiveChallenge:completionHandler:
方法腮恩。
NSURLSessionTaskDelegate
// 請求改變的時候調(diào)用
- (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);
}
}
// 請求需要一個全新的,未打開的數(shù)據(jù)時調(diào)用温兼。特別是請求一個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);
}
}
// 上傳數(shù)據(jù)時候調(diào)用
- (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);
}
}
// 完成時調(diào)用
- (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
// 收到響應(yīng)時調(diào)用
- (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變?yōu)镹SURLSessionDownloadTask調(diào)用,之后NSURLSessionDataTask將不再接受消息
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
[self removeDelegateForTask:dataTask];
// 重新設(shè)置代理
[self setDelegate:delegate forTask:downloadTask];
}
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
// 接受數(shù)據(jù)過程中募判,調(diào)用荡含,只限于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);
}
}
// 即將緩存響應(yīng)時調(diào)用
- (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);
}
}
// 后臺任務(wù)完成成后
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
NSURLSessionDownloadDelegate
// 下載完成后調(diào)用
- (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];
}
}
// 下載中調(diào)用
- (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);
}
}
// 回復(fù)下載時調(diào)用,使用fileOffset實現(xiàn)
- (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已經(jīng)解讀了5篇了装处,所有的核心類也解釋完畢误债,下一篇文章會是AFHTTPSessionManager這個類了 。我們最終的目標是寫一個通用的包含大部分功能的網(wǎng)絡(luò)框架妄迁,這個需要在解讀完剩余的類之后再實現(xiàn)寝蹈。我會演示一個從無到有的網(wǎng)絡(luò)框架的產(chǎn)生過程。**