一. 前言
AFNetWorking的使用率非常高齐媒,這個(gè)不必多說蒲每。無論是AFNetWorking還是SDWebImage等涉及到網(wǎng)絡(luò)請(qǐng)求的,底層都是封裝了原生的網(wǎng)絡(luò)請(qǐng)求喻括。如果對(duì)原生的網(wǎng)絡(luò)請(qǐng)求還不熟悉的話邀杏,可以看看我之前寫的這篇文章【iOS小結(jié)】NSURLSession。
AFNetWorking的功能很多唬血,我這篇主要解讀的是網(wǎng)絡(luò)請(qǐng)求(關(guān)于AFNetWorking怎么封裝網(wǎng)絡(luò)請(qǐng)求的知識(shí))望蜡。
二. AFNetWorking的網(wǎng)絡(luò)請(qǐng)求
網(wǎng)絡(luò)請(qǐng)求涉及的類主要是這兩個(gè):
- AFHTTPSessionManager:擁有生成任務(wù),管理任務(wù)的功能拷恨,并提供HTTP方法的API接口
- AFURLSessionManager:封裝了生成任務(wù)脖律,管理任務(wù)
我們先來看一個(gè)簡(jiǎn)單的POST請(qǐng)求,我們只需把urlString
和parameters
傳進(jìn)去腕侄,AFHTTPSessionManager就會(huì)創(chuàng)建一個(gè)POST的請(qǐng)求任務(wù)小泉,并把請(qǐng)求進(jìn)度芦疏,請(qǐng)求成功,請(qǐng)求失敗通過Block回調(diào)給你微姊。
簡(jiǎn)單來說酸茴,其內(nèi)部的實(shí)現(xiàn)其實(shí)是會(huì)通過這兩個(gè)參數(shù)生成對(duì)應(yīng)的NSURLRequest,并通過NSURLSession創(chuàng)建請(qǐng)求任務(wù)兢交,遵守相關(guān)的NSURLSession的協(xié)議薪捍,在對(duì)應(yīng)的協(xié)議方法里面進(jìn)行判斷處理,回調(diào)配喳。
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//設(shè)置URL
NSString *urlString = @"www.baidu.com/login/info";
//設(shè)置參數(shù)
NSDictionary *parameters = @{@"username":@"Tom",@"pwd":@"123"};
//發(fā)送post請(qǐng)求
[manager POST:urlString parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
//上傳過程中的回調(diào)
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//成功的回調(diào)
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//失敗的回調(diào)
}];
接下來我們來一步步分析:
AFHTTPSessionManager
當(dāng)使用AFNetWorking進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí)酪穿,我們會(huì)創(chuàng)建AFHTTPSessionManager對(duì)象,進(jìn)行對(duì)應(yīng)的HTTP請(qǐng)求界逛。AFHTTPSessionManager繼承于AFURLSessionManager昆稿,除了AFURLSessionManager的功能外,還提供HTTP方法的接口息拜。
以下紅框是AFHTTPSessionManager提供的方法溉潭,以及對(duì)應(yīng)的用途:
我們?cè)賮砜淳唧w提供的方法:
//GET方法
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
//GET方法(帶下載進(jìn)度)
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//HEAD方法
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//POST方法
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
//POST方法(帶上傳進(jìn)度)
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//POST方法(帶構(gòu)造體Block)→ 用于Multipart/form-data
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
//POST方法(帶構(gòu)造體Block和上傳進(jìn)度)→ 用于Multipart/form-data
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//PUT方法
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//PATCH方法
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//DELETE方法
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
其中比較特殊的是POST方法,POST帶構(gòu)造體Block的方法是用于Multipart/form-data少欺,用于上傳文件喳瓣。具體原理我就不多解釋,可以看看這個(gè)例子AFNetworking文件上傳和Multipart/form-data POST文件上傳詳解
赞别。
另外畏陕,這兩種方法創(chuàng)建的是uploadTask(通過文件流上傳)。在下面方法中通過請(qǐng)求序列化對(duì)象self.requestSerializer
將url仿滔、參數(shù)和構(gòu)造體Block序列化成對(duì)應(yīng)的request惠毁,然后通過該request去創(chuàng)建對(duì)應(yīng)的uploadTask。
// POST請(qǐng)求(帶構(gòu)造體block崎页,帶進(jìn)度鞠绰,用于Multipart/form-data,上傳)
// 用于Multipart/form-data上傳的基礎(chǔ)方法
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
//序列化request(構(gòu)建)
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
//判斷序列化是否成功
if (serializationError) {
//失敗的話就通過failure回調(diào)飒焦,返回nil
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
//創(chuàng)建uploadTask(通過文件流上傳文件)
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
而其他方法創(chuàng)建的是dataTask蜈膨。也是通過請(qǐng)求序列化對(duì)象self.requestSerializer
將url、參數(shù)序列化成對(duì)應(yīng)的request牺荠,然后通過該request去創(chuàng)建對(duì)應(yīng)的dataTask翁巍。
// 所有請(qǐng)求的基礎(chǔ)方法(通過改變method參數(shù))
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
//把參數(shù),還有各種東西轉(zhuǎn)換為一個(gè)request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
//轉(zhuǎn)換錯(cuò)誤就在failure中回調(diào)
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//如果解析錯(cuò)誤休雌,直接返回
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
//生成一個(gè)dataTask
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
總之灶壶,AFHTTPSessionManager封裝了幾種我們常用的網(wǎng)絡(luò)請(qǐng)求以及文件上傳的接口(當(dāng)然也是不全的,比如少了downloadTask杈曲,這邊就需要我們直接使用AFHTTPRequestSerializer去創(chuàng)建)例朱。
通過AFHTTPRequestSerializer對(duì)象通過我們傳入的參數(shù)轉(zhuǎn)化成對(duì)應(yīng)的NSMutableURLRequest孝情。然后通過request調(diào)用其父類AFURLSessionManager創(chuàng)建對(duì)應(yīng)的task。當(dāng)然也把對(duì)應(yīng)的success和failure等回調(diào)傳進(jìn)去洒嗤。
另外箫荡,AFHTTPSessionManager有三個(gè)屬性在初始化時(shí)會(huì)被創(chuàng)建,baseURL好理解渔隶,requestSerializer是用來序列化(構(gòu)建)request的羔挡,
responseSerializer是用來處理響應(yīng)數(shù)據(jù)的。requestSerializer是AFHTTPSessionManager特有的屬性间唉,因?yàn)锳FHTTPSessionManager賦值構(gòu)建request绞灼,然后生成對(duì)應(yīng)的task,而responseSerializer實(shí)際上是用在AFURLSessionManager呈野,因?yàn)樗沁呚?fù)責(zé)生成管理任務(wù)低矮,并且對(duì)結(jié)果進(jìn)行處理。
self.baseURL = url;
// 設(shè)置請(qǐng)求和響應(yīng)序列化對(duì)象
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
AFURLSessionManager
其實(shí)從AFHTTPSessionManager先分析被冒,再分析AFURLSessionManager是有弊端的军掂,因?yàn)锳FHTTPSessionManager繼承于AFURLSessionManager,所以AFHTTPSessionManager的很多功能只有留到講解AFURLSessionManager的時(shí)候才能說明昨悼。
之前AFHTTPSessionManager的分析說過蝗锥,AFURLSessionManager封裝了生成、管理任務(wù)率触。AFHTTPSessionManager只需要生成對(duì)應(yīng)request终议,通過request調(diào)用AFURLSessionManager生成task的方法。
AFURLSessionManager是一個(gè)怎么樣的類呢葱蝗?
這邊的探究分成兩部分:
① 提供什么功能(其實(shí)還是跟任務(wù)有關(guān))→看.h
② 怎么實(shí)現(xiàn)這些功能 →看.m
① AFURLSessionManager提供的功能
AFURLSessionManager提供的功能也是之前AFHTTPSessionManager未講到的功能穴张。AFURLSessionManager主要是封裝了生成任務(wù)和管理任務(wù),AFURLSessionManager實(shí)際上是NSURLSession對(duì)象的組合體两曼,提供的功能自然也是圍繞著任務(wù)皂甘。
AFURLSessionManager提供的功能主要有三種:
1)獲取正在執(zhí)行的所有任務(wù)、請(qǐng)求任務(wù)合愈、上傳任務(wù)叮贩、下載任務(wù)击狮。
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;
2)提供多種創(chuàng)建task的方法(封裝了所有NSURLSession創(chuàng)建task的方法)
3)提供了對(duì)NSURLSession協(xié)議的自定義處理的Block佛析。(類似于代理)
② AFURLSessionManager怎么實(shí)現(xiàn)這些功能
AFURLSessionManager.m的內(nèi)容其實(shí)挺多的,分開來看其實(shí)主要有四部分內(nèi)容(最主要還是第四部分):
1)單例創(chuàng)建各種隊(duì)列的C函數(shù)彪蓬。定義各種key寸莫,Notification的值。定義各種Block档冬。(該部分也好理解膘茎,就是為了讓下面來使用)
2)_AFURLSessionTaskSwizzling(主要用來交換NSURLSessionTask的Resume和Suspend方法)→主要是為了添加通知的代碼桃纯?
+ (void)load {
/**
WARNING: 高能預(yù)警
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
// 擔(dān)心以后iOS中不存在NSURLSessionTask
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7和iOS 8在NSURLSessionTask實(shí)現(xiàn)上有些許不同,這使得下面的代碼實(shí)現(xiàn)略顯trick
關(guān)于這個(gè)問題披坏,大家做了很多Unit Test态坦,足以證明這個(gè)方法是可行的
目前我們所知的:
- NSURLSessionTasks是一組class的統(tǒng)稱,如果你僅僅使用提供的API來獲取NSURLSessionTask的class棒拂,并不一定返回的是你想要的那個(gè)(獲取NSURLSessionTask的class目的是為了獲取其resume方法)
- 簡(jiǎn)單地使用[NSURLSessionTask class]并不起作用伞梯。你需要新建一個(gè)NSURLSession,并根據(jù)創(chuàng)建的session再構(gòu)建出一個(gè)NSURLSessionTask對(duì)象才行帚屉。
- iOS 7上谜诫,localDataTask(下面代碼構(gòu)造出的NSURLSessionDataTask類型的變量,為了獲取對(duì)應(yīng)Class)的類型是 __NSCFLocalDataTask攻旦,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask喻旷,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask。
- iOS 8上牢屋,localDataTask的類型為__NSCFLocalDataTask且预,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自NSURLSessionTask
- iOS 7上伟阔,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個(gè)實(shí)現(xiàn)了resume和suspend方法的類辣之,另外__NSCFLocalSessionTask中的resume和suspend并沒有調(diào)用其父類(即__NSCFURLSessionTask)方法,這也意味著兩個(gè)類的方法都需要進(jìn)行method swizzling皱炉。
- iOS 8上怀估,NSURLSessionTask是唯一實(shí)現(xiàn)了resume和suspend方法的類。這也意味著其是唯一需要進(jìn)行method swizzling的類
- 因?yàn)镹SURLSessionTask并不是在每個(gè)iOS版本中都存在合搅,所以把這些放在此處(即load函數(shù)中)多搀,比如給一個(gè)dummy class添加swizzled方法都會(huì)變得很方便,管理起來也方便灾部。
一些假設(shè)前提:
- 目前iOS中resume和suspend的方法實(shí)現(xiàn)中并沒有調(diào)用對(duì)應(yīng)的父類方法康铭。如果日后iOS改變了這種做法,我們還需要重新處理
- 沒有哪個(gè)后臺(tái)task會(huì)重寫resume和suspend函數(shù)
*/
// 1) 首先構(gòu)建一個(gè)NSURLSession對(duì)象session赌髓,再通過session構(gòu)建出一個(gè)_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實(shí)現(xiàn)的指針
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
// 3) 檢查當(dāng)前class是否實(shí)現(xiàn)了resume从藤。如果實(shí)現(xiàn)了,繼續(xù)第4步锁蠕。
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4) 獲取到當(dāng)前class的父類(superClass)
Class superClass = [currentClass superclass];
// 5) 獲取到當(dāng)前class對(duì)于resume實(shí)現(xiàn)的指針
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
// 6) 獲取到父類對(duì)于resume實(shí)現(xiàn)的指針
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
// 7) 如果當(dāng)前class對(duì)于resume的實(shí)現(xiàn)和父類不一樣(類似iOS7上的情況)夷野,并且當(dāng)前class的resume實(shí)現(xiàn)和af_resume不一樣,才進(jìn)行method swizzling荣倾。
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
// 8) 設(shè)置當(dāng)前操作的class為其父類class悯搔,重復(fù)步驟3~8
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
- (NSURLSessionTaskState)state {
//在實(shí)際的虛擬類中不應(yīng)調(diào)用狀態(tài)方法。
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];
//執(zhí)行resume方法
[self af_resume];
//如果之前是其他狀態(tài)舌仍,就變回resume狀態(tài)妒貌,發(fā)出DidResume的通知
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];
//執(zhí)行suspend方法
[self af_suspend];
//如果之前是其他狀態(tài)通危,就變回suspend狀態(tài),發(fā)出DidSuspend的通知
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
3)AFURLSessionManagerTaskDelegate(task代理類)
為什么是task的代理類呢灌曙?主要是因?yàn)閠ask將它將處理獲取數(shù)據(jù)菊碟,監(jiān)聽進(jìn)度的職責(zé)放在該類去執(zhí)行,AFURLSessionManagerTaskDelegate是task的代理人在刺。我們先來看AFURLSessionManager里面的方法:(同理對(duì)應(yīng)的uploadTask和downloadTask也有設(shè)置的方法)
//設(shè)置普通請(qǐng)求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
{
// 創(chuàng)建一個(gè)代理人
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//為task設(shè)置代理(把dataTask傳給delegate)
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
可以說task該處理的回調(diào)都放在代理類這邊處理了框沟。我們?cè)倏纯催@個(gè)代理類怎么處理數(shù)據(jù)和監(jiān)聽進(jìn)度。
關(guān)于進(jìn)度的監(jiān)聽增炭,AFURLSessionManagerTaskDelegate設(shè)置了兩個(gè)NSProgress對(duì)象(uploadProgress和downloadProgress)來分別監(jiān)聽上傳和下載進(jìn)度忍燥。(關(guān)于NSProgress的作用可以看這篇文章進(jìn)度: NSProgress)
簡(jiǎn)單說起來,NSProgress是一個(gè)記錄進(jìn)度的類隙姿,在需要記錄多個(gè)進(jìn)度有很大的優(yōu)勢(shì)梅垄。uploadProgress和downloadProgress除了記錄進(jìn)度外,通過設(shè)置也可以控制task的生命周期输玷。
另外队丝,通過對(duì)task接收到的字節(jié)數(shù)、期望接收到的字節(jié)數(shù)欲鹏、發(fā)送的字節(jié)數(shù)机久、期望發(fā)送的字節(jié)數(shù)進(jìn)行監(jiān)聽,值改變時(shí)去設(shè)置uploadProgress或downloadProgress對(duì)應(yīng)的總值或完成值赔嚎。通過對(duì)uploadProgress和downloadProgress完成度的監(jiān)聽膘盖,通過對(duì)應(yīng)的uploadProgressBlock和downloadProgressBlock將進(jìn)度信息回調(diào)出去。
//為task設(shè)置進(jìn)度(task的進(jìn)度會(huì)受到監(jiān)聽)
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask = task;
//設(shè)置上傳和下載進(jìn)度條的總數(shù)目尤误,暫停侠畔,取消,回復(fù)回調(diào)
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];
}];
}
//對(duì)task接收到的字節(jié)數(shù)损晤、期望接收到的字節(jié)數(shù)软棺、發(fā)送的字節(jié)數(shù)、期望發(fā)送的字節(jié)數(shù)進(jìn)行監(jiān)聽
[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];
//對(duì)上傳和下載完成的分?jǐn)?shù)(NSProgress的fractionCompleted)進(jìn)行監(jiān)聽
[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的進(jìn)度
- (void)cleanUpProgressForTask:(NSURLSessionTask *)task {
//移除task接收到的字節(jié)數(shù)尤勋、期望接收到的字節(jié)數(shù)喘落、發(fā)送的字節(jié)數(shù)、期望發(fā)送的字節(jié)數(shù)的監(jiān)聽
[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))];
//移除上傳最冰,下載進(jìn)度條完成的分?jǐn)?shù)的監(jiān)聽
[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 {
//如果是task的監(jiān)聽
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];
}
}
//如果是progress的監(jiān)聽
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)于數(shù)據(jù)的處理(當(dāng)然也包括對(duì)應(yīng)成功失敗的回調(diào))瘦棋,主要是實(shí)現(xiàn)那幾個(gè)有獲取到數(shù)據(jù)的NSURLSession的代理方法,這幾個(gè)代理方法是通過AFURLSessionManager來調(diào)用的锌奴,這個(gè)后續(xù)會(huì)提到兽狭。
關(guān)于數(shù)據(jù)最終的回調(diào)都是在- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
中處理憾股,另外比如dowmloadTask還需要處理下載中拼接數(shù)據(jù)和下載完成移動(dòng)文件夾的情況鹿蜀。
task完成時(shí)箕慧,除了將數(shù)據(jù)、響應(yīng)茴恰、錯(cuò)誤通過completionHandler回調(diào)出去颠焦,還發(fā)出DidComplete通知,傳出去userInfo(保存著請(qǐng)求和響應(yīng)序列化對(duì)象鈍往枣、數(shù)據(jù)伐庭、文件路徑、錯(cuò)誤信息等)分冈。有一點(diǎn)特別的是completionHandler會(huì)在線程組中回調(diào)圾另,我想這邊是為了讓開發(fā)者可以來設(shè)定監(jiān)聽。
#pragma mark - NSURLSessionTaskDelegate
//請(qǐng)求完成的回調(diào)
//處理完成后的數(shù)據(jù)
- (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];
// 把響應(yīng)序列化對(duì)象保存在userInfo
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
//把mutableData的值拿出來雕沉,并清空
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;
}
// 把下載路徑和下載數(shù)據(jù)保存在userInfo
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
// 如果完成的回調(diào)有錯(cuò)誤信息
if (error) {
// 把錯(cuò)誤信息也保存在userInfo
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
//把所有完成的回調(diào)放在completionQueue的線程組中
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ā)送任務(wù)完成的通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {//沒有錯(cuò)誤信息
//在并行隊(duì)列異步執(zhí)行
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
//創(chuàng)建響應(yīng)對(duì)象(解析成我們需要的數(shù)據(jù))
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
//如果有下載路徑就是下載集乔,只要返回下載路徑就可以
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
// 把響應(yīng)對(duì)象保存在userInfo
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
// 把序列化錯(cuò)誤信息保存在userInfo
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
//把所有完成的回調(diào)放在completionQueue的線程組中
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);
}
//發(fā)出任務(wù)完成的通知(userInfo附帶任務(wù)中的所有信息)
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
#pragma clang diagnostic pop
}
#pragma mark - NSURLSessionDataTaskDelegate
//開始受到數(shù)據(jù)的回調(diào)
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
//拼接數(shù)據(jù)
[self.mutableData appendData:data];
}
#pragma mark - NSURLSessionDownloadTaskDelegate
//完成下載的回調(diào)
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
//獲取用戶指定的文件儲(chǔ)存的url地址
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
//將儲(chǔ)存在臨時(shí)目錄下的文件移動(dòng)到用戶指定的url地址
[[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
//如果移動(dòng)失敗就發(fā)出通知(移動(dòng)失敗了。坡椒。扰路。)
if (fileManagerError) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
4)AFURLSessionManager(主要類,實(shí)現(xiàn)功能)
AFURLSessionManager包含一個(gè)NSURLSession屬性倔叼,生成任務(wù)本質(zhì)還是調(diào)用NSURLSession生成任務(wù)的方法,我們以創(chuàng)建dataTask為例汗唱,主要做了兩個(gè)步驟,加鎖通過session創(chuàng)建dataTask丈攒,為創(chuàng)建的dataTask設(shè)置代理人(AFURLSessionManagerTaskDelegate)哩罪。
#pragma mark - 普通請(qǐng)求任務(wù)
// 普通請(qǐng)求任務(wù)(帶完成的回調(diào))
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
}
// 普通請(qǐng)求任務(wù)(帶完成的回調(diào),以及上傳進(jìn)度和下載進(jìn)度的回調(diào))
- (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;
//在串行隊(duì)列中同步生成task(類似加鎖)
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
//為生成的普通請(qǐng)求task添加代理
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
我們?cè)賮砜纯淳唧w怎么設(shè)置代理人巡验,這邊的代理跟我們之前的代理協(xié)議啥的不一樣识椰,這邊的代理人無非是AFURLSessionManager去調(diào)用代理類的方法(比如設(shè)置進(jìn)度,添加通知深碱,甚至后面的NSURLSession的代理方法)腹鹉。一個(gè)任務(wù)對(duì)應(yīng)一個(gè)代理類。另外敷硅,AFURLSessionManager還會(huì)將代理人保存在字典中功咒,key是對(duì)應(yīng)task的ID。
還有一點(diǎn)重要的是绞蹦,為task添加通知(TaskDidResume和DidSuspend)力奋,這樣就會(huì)剔除其他外來的任務(wù),讓該類產(chǎn)生的任務(wù)發(fā)出對(duì)應(yīng)的通知幽七。
//設(shè)置普通請(qǐng)求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
{
// 創(chuàng)建一個(gè)代理人
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//為task設(shè)置代理(把dataTask傳給delegate)
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
//為task設(shè)置代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
// 斷言判斷參數(shù)是否為空
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
// 以task的id為key景殷,AFURLSessionManagerTaskDelegate為value儲(chǔ)存起來
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 代理人為task設(shè)置進(jìn)度
[delegate setupProgressForTask:task];
//給task添加恢復(fù)和暫停的通知(AFNSURLSessionTaskDidResumeNotification和AFNSURLSessionTaskDidSuspendNotification)
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
那什么時(shí)候移除代理呢?那就是任務(wù)完成的時(shí)候。在這邊我們也可以看到task完成的代理方法執(zhí)行時(shí)猿挚,主動(dòng)去獲取對(duì)應(yīng)task的代理咐旧,讓他去執(zhí)行對(duì)應(yīng)操作。
//task完成時(shí)調(diào)用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// 當(dāng)在后臺(tái)完成任務(wù)時(shí)绩蜻,委托可能是nil
if (delegate) {
// 調(diào)用代理對(duì)應(yīng)的方法(讓代理去處理)
[delegate URLSession:session task:task didCompleteWithError:error];
// task完成了铣墨,移除代理
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
前面我們只是講了生成dataTask,對(duì)應(yīng)的還有downloadTask办绝,uploadTask步驟也差不多(NSURLSession生成task伊约,添加代理)。既然生成task孕蝉,也遵守了其協(xié)議屡律,肯定實(shí)現(xiàn)了NSURLSession的代理方法,就如上面提到的AFURLSessionManager提供了對(duì)NSURLSession協(xié)議的自定義處理的Block(類似于代理)降淮。
這個(gè)意思是在代理方法中你可以選擇默認(rèn)的處理或自定義處理(實(shí)現(xiàn)自定義處理的Block)疹尾。以收到dataTask收到數(shù)據(jù)的代理方法為例,默認(rèn)的處理是讓代理類去處理骤肛,進(jìn)行數(shù)據(jù)的拼接纳本,我們也可以實(shí)現(xiàn)dataTaskDidReceiveData,自己處理(這種如果用代理實(shí)現(xiàn)應(yīng)該好理解一點(diǎn))腋颠。
//dataTask收到數(shù)據(jù)時(shí)調(diào)用
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
//調(diào)用代理對(duì)應(yīng)的方法(讓代理去處理)
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
//自己處理
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
關(guān)于上面提到的(2)和(3)功能都說完了繁成,我們來說說第一個(gè)功能(1)獲取正在執(zhí)行的所有任務(wù)、請(qǐng)求任務(wù)淑玫、上傳任務(wù)巾腕、下載任務(wù)。其實(shí)就是使用session的getTasksWithCompletionHandler
方法絮蒿。
#pragma mark - 獲取當(dāng)前運(yùn)行的任務(wù)
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
// 創(chuàng)建一個(gè)信號(hào)量尊搬,值為0
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"];
}
//信號(hào)量加1
dispatch_semaphore_signal(semaphore);
}];
//一直等待,知道信號(hào)量不為0才會(huì)執(zhí)行到這一步
//將計(jì)數(shù)值減1,并且執(zhí)行返回
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
// 獲取當(dāng)前正在運(yùn)行的tasks
- (NSArray *)tasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
// 獲取當(dāng)前正在運(yùn)行的dataTasks
- (NSArray *)dataTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
// 獲取當(dāng)前正在運(yùn)行的uploadTasks
- (NSArray *)uploadTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
// 獲取當(dāng)前正在運(yùn)行的downloadTasks
- (NSArray *)downloadTasks {
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}