該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請(qǐng)注明:劉小壯
AFNetworking
是iOS
最常用的網(wǎng)絡(luò)框架观挎,雖然系統(tǒng)也有NSURLSession
午衰,但是我們一般不會(huì)直接用它括改。AFNetworking
經(jīng)過了三個(gè)大版本穴翩,現(xiàn)在用的大多數(shù)都是3.x的版本。
AFNetworking
經(jīng)歷了下面三個(gè)階段的發(fā)展:
- 1.0版本 : 基于
NSURLConnection
的封裝诀姚。 - 2.0版本 : 兩套實(shí)現(xiàn),分別基于
NSURLConnection
和NSURLSession
玷禽,是轉(zhuǎn)向NSURLSession
的過渡版赫段。 - 3.0版本 : 基于
NSURLSession
的封裝。
文件構(gòu)成
AFNetworking3.X
的構(gòu)成很簡(jiǎn)單矢赁,主要就四部分糯笙,除此之外還有一些基于UIKit
的Category
,但這些并不是標(biāo)配撩银。
- Manager : 負(fù)責(zé)處理網(wǎng)絡(luò)請(qǐng)求的兩個(gè)
Manager
给涕,主要實(shí)現(xiàn)都在AFURLSessionManager
中。 - Reachability : 網(wǎng)絡(luò)狀態(tài)監(jiān)控。
- Security : 處理網(wǎng)絡(luò)安全和
HTTPS
相關(guān)的够庙。 - Serialization : 請(qǐng)求和返回?cái)?shù)據(jù)的格式化器恭应。
AFURLSessionManager
在AFN3.0
中,網(wǎng)絡(luò)請(qǐng)求的manager
主要有AFHTTPSessionManager
和AFURLSessionManager
構(gòu)成耘眨,二者為父子關(guān)系昼榛。這兩個(gè)類職責(zé)劃分很清晰,父類負(fù)責(zé)處理一些基礎(chǔ)的網(wǎng)絡(luò)請(qǐng)求代碼毅桃,并且接受NSURLRequest
對(duì)象褒纲,而子類則負(fù)責(zé)處理和http
協(xié)議有關(guān)的邏輯。
AFN
的這套設(shè)計(jì)很便于擴(kuò)展钥飞,如果以后想增加FTP
協(xié)議的處理莺掠,則基于AFURLSessionManager
創(chuàng)建子類即可。子類中只需要進(jìn)行很少的代碼處理读宙,創(chuàng)建一個(gè)NSURLRequest
對(duì)象后調(diào)用父類代碼彻秆,由父類去完成具體的請(qǐng)求操作。
創(chuàng)建sessionManager
AFHTTPSessionManager
類的初始化方法中并沒有太多實(shí)現(xiàn)代碼结闸,其內(nèi)部調(diào)用的都是父類AFURLSessionManager
的initWithSessionConfiguration
方法唇兑,下面是此方法內(nèi)部的一些關(guān)鍵代碼。
在初始化方法中包含一個(gè)參數(shù)sessionConfiguration
桦锄,如果沒有傳入的話默認(rèn)是使用系統(tǒng)的defaultConfiguration
扎附,我們創(chuàng)建是一般都不會(huì)自定義configuration
,所以大多數(shù)都是系統(tǒng)的结耀。
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
隨后是NSURLSession
的初始化代碼留夜,關(guān)于NSOperationQueue
后面詳細(xì)進(jìn)行講解。NSURLSession
的初始化方式有兩種图甜,一種是使用系統(tǒng)的共享session
碍粥,另一種是自己創(chuàng)建session
。AFN
選擇的是創(chuàng)建自己的session
黑毅,并且每個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)獨(dú)立的session
嚼摩。
可以通過NSURLSession
進(jìn)行連接復(fù)用,這樣可以避免很多握手和揮手的過程矿瘦,提高網(wǎng)絡(luò)請(qǐng)求速度枕面,蘋果允許iOS
設(shè)備上一個(gè)域名可以有四個(gè)連接同時(shí)存在。但是由于AFN
的實(shí)現(xiàn)是每個(gè)請(qǐng)求都創(chuàng)建一個(gè)session
匪凡,所以就不能進(jìn)行連接復(fù)用膊畴。
所以可以通過在外面對(duì)AFN
進(jìn)行二次封裝,將AFHTTPSessionManager
復(fù)用為單例對(duì)象病游,通過復(fù)用sessionManager
的方式唇跨,來進(jìn)行連接的復(fù)用稠通。但是這種方案對(duì)于不同的requestSerializer
、responseSerializer
等情況买猖,還是要做特殊兼容改橘,所以最好建立一個(gè)sessionManager
池,對(duì)于同類型的sessionManager
直接拿出來復(fù)用玉控,否則就創(chuàng)建新的飞主。
// 共享session連接池
[NSURLSession sharedSession];
// 創(chuàng)建新session,則不能使用共享session連接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
由于當(dāng)前AFURLSessionManager
對(duì)象的所有sessionTask
請(qǐng)求任務(wù)高诺,都是共享同一個(gè)回調(diào)代理的碌识,所以AFN
為了區(qū)分每個(gè)sessionTask
,通過下面的可變字典虱而,將所有taskDelegate
和task.taskIdentifier
的進(jìn)行了一一對(duì)應(yīng)筏餐,以便于很容易的對(duì)每個(gè)請(qǐng)求task
進(jìn)行操作。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
在初始化方法中牡拇,可以發(fā)現(xiàn)AFN
在創(chuàng)建session
后魁瞪,調(diào)用了getTasksWithCompletionHandler
方法來獲取當(dāng)前所有的task
。但是現(xiàn)在剛創(chuàng)建session
惠呼,理論上來說是不應(yīng)該有task
的导俘。但從AFN
的issues
中找到了答案issues 3499。
這是因?yàn)樘尢#?code>completionHandler回調(diào)中旅薄,為了防止進(jìn)入前臺(tái)時(shí),通過session id
恢復(fù)的task
導(dǎo)致一些崩潰問題泣崩,所以這里將之前的task
進(jìn)行遍歷赋秀,并將回調(diào)都置nil
。
[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];
}
}];
創(chuàng)建task
在AFURLSessionManager
中進(jìn)行task
的創(chuàng)建律想,task
的類型總共分為三種,dataTask
绍弟、uploadTask
技即、downloadTask
,AFN
并沒有對(duì)streamTask
進(jìn)行處理樟遣。
AFHTTPSessionManager
在創(chuàng)建GET
而叼、POST
等請(qǐng)求時(shí),本質(zhì)上都是調(diào)用了下面的方法或其類似的方法豹悬,方法內(nèi)部會(huì)創(chuàng)建一個(gè)task
對(duì)象葵陵,并調(diào)用addDelegateForDataTask
將后面的處理交給AFURLSessionManagerTaskDelegate
來完成。隨后會(huì)將task
返回給調(diào)用方瞻佛,調(diào)用方獲取到task
對(duì)象后脱篙,也就是子類AFHTTPSessionManager
娇钱,會(huì)調(diào)用resume
方法開始請(qǐng)求。
- (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;
}
除了普通請(qǐng)求外绊困,upload
文搂、download
都有類似的處理。
在addDelegateForDataTask
方法中秤朗,會(huì)調(diào)用sessionManager
的setDelegate:forTask:
方法煤蹭,此方法內(nèi)部將task
和taskDelegate
進(jìn)行了注冊(cè)。由于AFN
可以通過通知讓外界監(jiān)聽請(qǐng)求狀態(tài)取视,所以在此方法中還監(jiān)聽了task
的resume
和suspend
事件硝皂,并在實(shí)現(xiàn)代碼中將事件廣播出去。
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
如果從AFHTTPSessionManager
的創(chuàng)建任務(wù)開始作谭,按代碼邏輯跟到這里稽物,發(fā)現(xiàn)其實(shí)AFN3.0
的請(qǐng)求代碼真的很簡(jiǎn)單,主要都集中在創(chuàng)建NSMutableURLRequest
那里丢早,其他都依賴于NSURLSession
姨裸,因?yàn)榇_實(shí)NSURLSession
的API
封裝程度比較好,也很好使用怨酝。
AFN3.0
的作用就是對(duì)NSURLSession
的封裝性比較好傀缩,你不用去寫太多重復(fù)性的代碼,并且可以很容易的通過block
得到回調(diào)結(jié)果农猬。
AFURLSessionManagerTaskDelegate
NSURLSession
的回調(diào)方法比較多赡艰,這里只針對(duì)一些關(guān)鍵代碼進(jìn)行講解,以及梳理整體回調(diào)邏輯斤葱,不一一列舉每個(gè)回調(diào)方法的作用慷垮,詳細(xì)源碼各位可以直接下載AFN
代碼查看。
在AFURLSessionManager
中揍堕,有一個(gè)AFURLSessionManagerTaskDelegate
類比較重要料身,這個(gè)類和sessionTask
是一一對(duì)應(yīng)的,負(fù)責(zé)處理sessionTask
請(qǐng)求的很多邏輯衩茸,NSURLSessionDelegate
的回調(diào)基本都轉(zhuǎn)發(fā)給taskDelegate
去處理了芹血。在NSURLSession
回調(diào)中處理了HTTPS
證書驗(yàn)證、下載進(jìn)度之類的楞慈,沒有太復(fù)雜的處理幔烛。
taskDelegate
的設(shè)計(jì)很不錯(cuò),可以將代理回調(diào)任務(wù)處理對(duì)象化囊蓝,也可以給AFURLSessionManager
類瘦身饿悬。比較理想的是直接將代理設(shè)置為taskDelegate
,但是由于會(huì)涉及一些AFURLSessionManager
自身的處理邏輯聚霜,所以才設(shè)計(jì)為消息傳遞的方式狡恬。
taskDelegate
的功能很簡(jiǎn)單珠叔,主要是NSData
數(shù)據(jù)的處理,NSProgress
上傳下載進(jìn)度的處理傲宜,以及通知參數(shù)的處理运杭。在進(jìn)行AFN
的下載處理時(shí),NSData
的數(shù)據(jù)拼接函卒、事件回調(diào)辆憔,及文件處理,都是由taskDelegate
來完成的报嵌。
下面是downloadTask
任務(wù)完成時(shí)的處理代碼虱咧,其他回調(diào)代碼就不一一列舉了。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
taskDelegate
中有一個(gè)很好的設(shè)計(jì)锚国,taskDelegate
并不直接在NSURLSession
的代理方法中做進(jìn)度拼接和回調(diào)腕巡。而是對(duì)于上傳和下載任務(wù)分別對(duì)應(yīng)不同的NSProgress
,并通過KVO
來監(jiān)聽fractionCompleted
屬性血筑,并且實(shí)現(xiàn)cancel
绘沉、suspend
等狀態(tài)回調(diào)。任務(wù)的狀態(tài)和進(jìn)度處理交給NSProgress
豺总,在回調(diào)方法中直接拼接NSProgress
的進(jìn)度车伞,從而回調(diào)KVO
方法。
NSProgress
內(nèi)部的cancel
喻喳、pause
另玖、resume
方法,正好可以對(duì)應(yīng)到sessionTask
的方法調(diào)用表伦。但是從代碼角度來看谦去,AFN
好像并沒有進(jìn)行相關(guān)的調(diào)用,但這個(gè)設(shè)計(jì)思路很好蹦哼。
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
#if AF_CAN_USE_AT_AVAILABLE
if (@available(iOS 9, macOS 10.11, *))
#else
if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
{
progress.resumingHandler = ^{
[weakTask resume];
};
}
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
_AFURLSessionTaskSwizzling
看過源碼的話鳄哭,可以發(fā)現(xiàn)AFURLSessionManager
中還有一個(gè)_AFURLSessionTaskSwizzling
類,這里我們簡(jiǎn)稱taskSwizzling
類纲熏。我認(rèn)為此類的設(shè)計(jì)實(shí)在是冗余窃诉,此類的主要功能就是在+load
方法中進(jìn)行一個(gè)swizzling
,將dataTask
的resume
和suspend
方法進(jìn)行替換赤套,并且在替換后的方法中發(fā)出對(duì)應(yīng)的通知,并沒有太多實(shí)際的功能珊膜。
只不過taskSwizzling
類中還是有一些不錯(cuò)的代碼設(shè)計(jì)值得借鑒的容握,由于sessionTask
存在一系列繼承鏈,所以直接對(duì)其進(jìn)行swizzling
對(duì)其他子類并不生效车柠,因?yàn)槊總€(gè)子類都有自己的實(shí)現(xiàn)剔氏,而寫一大堆swizzling
又沒有什么技術(shù)含量塑猖。
在iOS7
和iOS8
上,sessionTask
的繼承關(guān)系并不一樣谈跛,最好進(jìn)行一個(gè)統(tǒng)一的處理羊苟。AFN
采取的方式是創(chuàng)建一個(gè)dataTask
對(duì)象,并對(duì)這個(gè)對(duì)象進(jìn)行swizzling
感憾,并且遍歷其繼承鏈一直進(jìn)行swizzling
蜡励,這樣保證集成繼承鏈的正確性。
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
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
+ (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));
}
}
clang預(yù)編譯指令
AFN
為了避免發(fā)生編譯器警告阻桅,采取了預(yù)編譯指令對(duì)代碼進(jìn)行修飾凉倚,預(yù)編譯指令基本由三部分組成,push
嫂沉、pop
稽寒、ignored
類型。Github
上有人維護(hù)了一份clang warning清單趟章,如果想進(jìn)行對(duì)應(yīng)的預(yù)編譯處理可以上去找找有沒有合適的杏糙。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
線程問題
NSURLSession
在iOS8
以下會(huì)并發(fā)創(chuàng)建多個(gè)task
,但并發(fā)設(shè)置task identifier
的時(shí)候會(huì)存在identifier
重復(fù)的問題蚓土。為了解決這個(gè)問題宏侍,在iOS8
以下,系統(tǒng)將所有sessionTask
的創(chuàng)建都放在一個(gè)同步的串行隊(duì)列中進(jìn)行北戏,保證創(chuàng)建及賦值操作是串行進(jìn)行的负芋。
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});
// 如果Foundation版本小于iOS8,則把block任務(wù)放在一個(gè)同步隊(duì)列中執(zhí)行嗜愈。這個(gè)問題是由于在iOS8以下并發(fā)創(chuàng)建任務(wù)旧蛾,可能會(huì)有多個(gè)相同的identifier
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
一個(gè)比較有意思的是,AFN
為了讓開發(fā)者明白為什么要加這個(gè)判斷蠕嫁,對(duì)iOS8
系統(tǒng)的判斷定義成了一個(gè)宏锨天,并且用Apple Support
的id
作為宏定義命名,很見名知意剃毒。
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
AFN
在回調(diào)didCompleteWithError
方法病袄,并處理返回?cái)?shù)據(jù)時(shí),會(huì)切換到其他線程和group
去處理赘阀,處理完成后再切換到主線程并通知調(diào)用方益缠。
AFN
提供了兩個(gè)屬性,用來設(shè)置請(qǐng)求結(jié)束后進(jìn)行回調(diào)的dispatch queue
和dispatch group
基公,如果不設(shè)置的話幅慌,AFN
會(huì)有默認(rèn)的實(shí)現(xiàn)來處理請(qǐng)求結(jié)束的操作。下面是group
和queue
的實(shí)現(xiàn)轰豆,AFN
對(duì)于返回?cái)?shù)據(jù)的處理胰伍,采用的是并發(fā)處理齿诞。
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;
}
NSOperationQueue
AFN
在創(chuàng)建AFURLSessionManager
的operationQueue
時(shí),將其最大并發(fā)數(shù)設(shè)置為1骂租。這是因?yàn)樵趧?chuàng)建NSURLSSession
時(shí)祷杈,蘋果要求網(wǎng)絡(luò)請(qǐng)求回來的數(shù)據(jù)順序執(zhí)行,為了保證代理方法的執(zhí)行順序渗饮,所以需要串行的調(diào)用NSURLSSession
的代理方法但汞。
AFHTTPSessionManager
AFHTTPSessionManager
本質(zhì)上是對(duì)父類AFURLSessionManager
的封裝,主要實(shí)現(xiàn)都在父類中抽米,自己內(nèi)部代碼實(shí)現(xiàn)很簡(jiǎn)單特占。在創(chuàng)建AFHTTPSessionManager
時(shí)會(huì)傳入一個(gè)baseURL
,以及指定requestSerializer
云茸、responseSerializer
對(duì)象是目。
從代碼實(shí)現(xiàn)來看,AFN
的請(qǐng)求并不是單例形式的标捺,每個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的請(qǐng)求對(duì)象懊纳。平時(shí)調(diào)用的GET
、POST
等網(wǎng)絡(luò)請(qǐng)求方法亡容,都定義在AFHTTPSessionManager
中嗤疯。AFHTTPSessionManager
內(nèi)部則調(diào)用父類方法,發(fā)起響應(yīng)的請(qǐng)求并獲取到task
對(duì)象闺兢,調(diào)用task
的resume
后返回給調(diào)用方茂缚。
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
AFURLRequestSerialization
AFURLRequestSerialization
負(fù)責(zé)創(chuàng)建NSMutableURLRequest
請(qǐng)求對(duì)象,并對(duì)request
進(jìn)行請(qǐng)求的參數(shù)拼接屋谭、設(shè)置緩存策略脚囊、設(shè)置請(qǐng)求頭等關(guān)于請(qǐng)求相關(guān)的配置。
AFURLRequestSerialization
并不是一個(gè)類桐磁,而是一個(gè)文件悔耘,其中包含三個(gè)requestSerializer
請(qǐng)求對(duì)象,分別對(duì)應(yīng)著不同的請(qǐng)求序列化器我擂。
- AFHTTPRequestSerializer:普通請(qǐng)求衬以。
- AFJSONRequestSerializer:
JSON
請(qǐng)求。 - AFPropertyListRequestSerializer:一種特殊的
xml
格式請(qǐng)求校摩。
這三個(gè)類區(qū)別就在于Content-Type
不同看峻,其他基本都是一樣的。AFN
默認(rèn)是HTTP
的。
AFURLRequestSerialization協(xié)議
在文件中定義了同名的AFURLRequestSerialization
協(xié)議,不同的requestSerializer
會(huì)對(duì)協(xié)議方法有不同的實(shí)現(xiàn),下面是AFHTTPRequestSerializer
的實(shí)現(xiàn)代碼持寄。其核心代碼實(shí)現(xiàn)也比較直觀,就是在創(chuàng)建requestSerializer
的時(shí)候玻佩,設(shè)置請(qǐng)求頭的公共參數(shù),以及將請(qǐng)求參數(shù)通過NSJSONSerialization
轉(zhuǎn)換為NSData
,并將其賦值給request
對(duì)象的httpBody
珠闰,下面是精簡(jiǎn)后的核心代碼。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
[mutableRequest setHTTPBody:jsonData];
}
return mutableRequest;
}
如果想給網(wǎng)絡(luò)請(qǐng)求設(shè)置請(qǐng)求參數(shù)的話瘫辩,需要通過requestSerializer
對(duì)外暴露的API
添加參數(shù)伏嗜,AFN
的requestManager
并不直接對(duì)外提供設(shè)置請(qǐng)求頭的代碼。通過requestSerializer
可以對(duì)請(qǐng)求頭進(jìn)行添加和刪除伐厌、以及清空的操作承绸。
從創(chuàng)建AFURLRequestSerialization
對(duì)象到最后返回NSURLRequest
對(duì)象,中間的過程并不復(fù)雜挣轨,主要是設(shè)置請(qǐng)求頭和拼接參數(shù)军熏,邏輯很清晰。
AFQueryStringPair
AFURLRequestSerialization
有一個(gè)很重要的功能就是參數(shù)處理卷扮,AFQueryStringPair
就是負(fù)責(zé)處理這些參數(shù)的荡澎。pair
類中定義了兩個(gè)屬性,分別對(duì)應(yīng)請(qǐng)求參數(shù)的key
晤锹、value
摩幔。除此之外,還定義了一些非常實(shí)用的C語言函數(shù)鞭铆。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (id)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
AFQueryStringFromParameters
函數(shù)負(fù)責(zé)將請(qǐng)求參數(shù)字典或衡,轉(zhuǎn)成拼接在URL
后面的參數(shù)字符串,這個(gè)函數(shù)是AFQueryStringPair
類中定義的一個(gè)關(guān)鍵函數(shù)车遂。函數(shù)內(nèi)部通過AFQueryStringPairsFromDictionary
函數(shù)將參數(shù)字典封断,轉(zhuǎn)為存儲(chǔ)pair
對(duì)象的數(shù)組并進(jìn)行遍歷,遍歷后調(diào)用URLEncodedStringValue
方法對(duì)參數(shù)進(jìn)行拼接艰额,最后成為字符串參數(shù)澄港。
URLEncodedStringValue
方法實(shí)現(xiàn)很簡(jiǎn)單,就是進(jìn)行一個(gè)key
柄沮、value
的拼接回梧,并且在中間加上“=”。
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
下面是參數(shù)拼接的代碼祖搓,函數(shù)內(nèi)部會(huì)將原有的參數(shù)狱意,轉(zhuǎn)換為AFQueryStringPair
對(duì)象的類型,但之前的層級(jí)結(jié)構(gòu)不變拯欧。這句話是什么意思呢详囤,就是說對(duì)原有傳入的對(duì)象進(jìn)行逐層遞歸調(diào)用,并且將最后一層字典的key
、value
參數(shù)藏姐,轉(zhuǎn)成pair
類型的對(duì)象隆箩,并且將嵌套有pair
對(duì)象的數(shù)組返回給調(diào)用方。
對(duì)象層級(jí)不變羔杨,但字典捌臊、集合都會(huì)被轉(zhuǎn)換為數(shù)組結(jié)構(gòu),也就是之前傳入字典兜材、數(shù)組理澎、字典的嵌套結(jié)構(gòu),返回的時(shí)候就是數(shù)組曙寡、數(shù)組糠爬、pair
的結(jié)構(gòu)返回。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
設(shè)置NSMutableURLRequest
AFHTTPRequestSerializer
在創(chuàng)建NSMutableURLRequest
時(shí)举庶,需要為request
設(shè)置屬性执隧。serializer
對(duì)外提供了和request
同名的一些屬性,外界直接調(diào)用serializer
即可設(shè)置request
的屬性灯变。
AFHTTPRequestSerializer
內(nèi)部創(chuàng)建request
時(shí)殴玛,并不是根據(jù)設(shè)置request
的屬性按個(gè)賦值,而是通過一個(gè)屬性數(shù)組AFHTTPRequestSerializerObservedKeyPaths
添祸,將serializer
需要賦值給request
的屬性滚粟,都放在數(shù)組中。
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
在初始化AFHTTPRequestSerializer
時(shí)刃泌,遍歷keyPath
數(shù)組并通過KVO
的方式凡壤,監(jiān)聽serializer
的賦值。如果外界對(duì)serializer
對(duì)應(yīng)的屬性進(jìn)行賦值耙替,則將其添加到mutableObservedChangedKeyPaths
數(shù)組中亚侠。在創(chuàng)建request
對(duì)象是,遍歷mutableObservedChangedKeyPaths
數(shù)組并將值賦值給request
對(duì)象俗扇。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
表單提交
當(dāng)進(jìn)行POST
表單提交時(shí)硝烂,需要用到AFMultipartFormData
協(xié)議。調(diào)用POST
方法后铜幽,會(huì)回調(diào)一個(gè)遵守此協(xié)議的對(duì)象滞谢,可以通過此對(duì)象進(jìn)行表單提交操作。
[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:params[@"front_img"]
name:@"front_img"
fileName:frontImgfileName
mimeType:@"multipart/form-data"];
[formData appendPartWithFileData:params[@"reverse_img"]
name:@"reverse_img"
fileName:reverseImgfileName
mimeType:@"multipart/form-data"];
[formData appendPartWithFileData:params[@"face_img"]
name:@"face_img"
fileName:faceImgfileName
mimeType:@"multipart/form-data"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
// nothing
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// nothing
} failure:nil];
進(jìn)行表單提交時(shí)除抛,可以直接傳入文件狮杨,也可以傳入路徑。表單提交可以同時(shí)提交多個(gè)文件到忽,理論上數(shù)量不受限制橄教。
緩存策略
AFN
的緩存策略和NSURLCache
的緩存策略一致,并且直接使用系統(tǒng)的枚舉,這對(duì)iOS
開發(fā)者是非常友好的护蝶。下面是枚舉定義华烟,忽略掉一些unimplemented
的,和一些重定向到已有枚舉的持灰,可用的都在這垦江。
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
NSURLRequestReloadRevalidatingCacheData = 5,
};
- NSURLRequestUseProtocolCachePolicy,使用協(xié)議指定的緩存策略搅方。
- NSURLRequestReloadIgnoringLocalCacheData,忽略緩存绽族,直接發(fā)起請(qǐng)求姨涡。
- NSURLRequestReturnCacheDataElseLoad,不驗(yàn)證緩存過期時(shí)間吧慢,如果有則使用緩存數(shù)據(jù)涛漂,如果不存在則請(qǐng)求服務(wù)器。
- NSURLRequestReturnCacheDataDontLoad检诗,不驗(yàn)證緩存過期時(shí)間匈仗,如果有則使用緩存數(shù)據(jù),如果不存在則請(qǐng)求失敗逢慌。
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData悠轩,忽略本地緩存,以及代理等中間介質(zhì)的緩存攻泼。
- NSURLRequestReloadRevalidatingCacheData火架,和數(shù)據(jù)的源服務(wù)器驗(yàn)證數(shù)據(jù)合法性,如果可以用就直接使用緩存數(shù)據(jù)忙菠,否則從服務(wù)器請(qǐng)求數(shù)據(jù)何鸡。
AFURLResponseSerialization
AFURLResponseSerialization
負(fù)責(zé)處理response
相關(guān)的邏輯,其功能主要是設(shè)置acceptType
牛欢、編碼格式和處理服務(wù)器返回?cái)?shù)據(jù)骡男。同樣的,AFURLResponseSerialization
也有同名的協(xié)議傍睹,每個(gè)子類都遵循代理方法并實(shí)現(xiàn)不同的返回值處理代碼隔盛。
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
和AFURLRequestSerialization
一樣,AFURLResponseSerialization
由一個(gè)父類和六個(gè)子類構(gòu)成焰望,子類中有一個(gè)是Mac
的骚亿,所以這里不做分析,子類的職責(zé)只是對(duì)acceptType
做修改以及處理具體的返回?cái)?shù)據(jù)熊赖。
- AFHTTPResponseSerializer:公共父類来屠,處理返回值類型為
NSData
二進(jìn)制。 - AFJSONResponseSerializer:
JSON
返回?cái)?shù)據(jù),也是默認(rèn)類型俱笛。 - AFXMLParserResponseSerializer捆姜,處理
XML
返回?cái)?shù)據(jù),由系統(tǒng)NSXMLParser
負(fù)責(zé)處理迎膜。 - AFPropertyListResponseSerializer:處理特殊
XML
返回?cái)?shù)據(jù)泥技,也就是plist
數(shù)據(jù)。 - AFImageResponseSerializer:處理圖片返回?cái)?shù)據(jù)磕仅,這個(gè)類型用的也比較多珊豹。
- AFCompoundResponseSerializer:處理復(fù)雜數(shù)據(jù),返回結(jié)果類型有多種榕订。
容錯(cuò)處理
由于服務(wù)器有時(shí)候會(huì)返回null
的情況店茶,系統(tǒng)會(huì)將其轉(zhuǎn)換為NSNull
對(duì)象,而對(duì)NSNull
對(duì)象發(fā)送不正確的消息劫恒,就會(huì)導(dǎo)致崩潰贩幻。從服務(wù)器接收到返回值后,AFN
會(huì)對(duì)返回值進(jìn)行一個(gè)遞歸查找两嘴,找到所有NSNull
對(duì)象并將其移除丛楚,防止出現(xiàn)向NSNull
對(duì)象發(fā)送消息導(dǎo)致的崩潰。
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}
AFNetworking的設(shè)計(jì)技巧
bundleForClass
在使用NSBundle
對(duì)象時(shí)憔辫,我們最常用的就是mainBundle
或者bundleWithPath
這種方式獲取bundle
趣些,這種對(duì)于都是從app
二進(jìn)制讀取的時(shí)候是沒有問題的。但是如果涉及到framework
動(dòng)態(tài)庫贰您,就不是那么易于使用喧务。
framework
中可以包含資源文件,例如.bundle
文件枉圃。如果是動(dòng)態(tài)庫形式的framework
(framework
也有靜態(tài)形式)功茴,其會(huì)以一個(gè)獨(dú)立二進(jìn)制的形式表現(xiàn),并且會(huì)分配獨(dú)立的二進(jìn)制空間孽亲。在讀取bundle
的時(shí)候坎穿,就可以考慮使用bundleForClass
的方式讀取。
bundleForClass
表示從當(dāng)前類定義的二進(jìn)制返劲,所在的程序包中讀取NSBundle
文件玲昧。例如.app
就是從main bundle
中讀取,如果是framework
就從其所在的二進(jìn)制中讀取篮绿。
網(wǎng)絡(luò)指示器
AFN
提供了一些UIKit
的Category
孵延,例如網(wǎng)絡(luò)請(qǐng)求發(fā)起時(shí),網(wǎng)絡(luò)指示器轉(zhuǎn)菊花亲配,則由AFNetworkActivityIndicatorManager
類負(fù)責(zé)尘应。開啟網(wǎng)絡(luò)指示器很簡(jiǎn)單惶凝,添加下面代碼即可,網(wǎng)絡(luò)指示器默認(rèn)是關(guān)閉的犬钢。
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
這里不對(duì)AFNetworkActivityIndicatorManager
的代碼進(jìn)行過多的分析苍鲜,只是調(diào)其中比較重要的點(diǎn)來分析,下面統(tǒng)稱為indicatorManager
玷犹。
之前在_AFURLSessionTaskSwizzling
類中寫了很多代碼混滔,就是為了發(fā)出resume
和suspend
兩個(gè)通知,這兩個(gè)通知在indicatorManager
中就用到了歹颓。網(wǎng)絡(luò)指示器監(jiān)聽了下面的三個(gè)通知坯屿,并且完全由通知來驅(qū)動(dòng)。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
如果看indicatorManager
中的源碼巍扛,你會(huì)發(fā)現(xiàn)為什么里面還有timer
愿伴,完全不需要啊,有網(wǎng)絡(luò)請(qǐng)求就轉(zhuǎn)菊花电湘,沒網(wǎng)絡(luò)請(qǐng)求就停止不就行了嗎?
這是因?yàn)?code>AFN考慮鹅经,如果一個(gè)網(wǎng)絡(luò)請(qǐng)求很快的話寂呛,會(huì)導(dǎo)致菊花出現(xiàn)轉(zhuǎn)一下很快就消失的情況,如果網(wǎng)絡(luò)請(qǐng)求比較多會(huì)多次閃現(xiàn)瘾晃。所以對(duì)于這個(gè)問題贷痪,indicatorManager
通過Timer
的方式實(shí)現(xiàn),如果在指定的區(qū)間內(nèi)網(wǎng)絡(luò)請(qǐng)求已經(jīng)結(jié)束蹦误,則不在顯示菊花劫拢,如果有多次請(qǐng)求則在請(qǐng)求之間也不進(jìn)行中斷。
對(duì)于開始轉(zhuǎn)圈設(shè)置的是1.0秒强胰,結(jié)束轉(zhuǎn)圈設(shè)置的是0.17秒舱沧。也就是當(dāng)菊花開始旋轉(zhuǎn)時(shí),需要有1.0秒的延時(shí)偶洋,這個(gè)時(shí)間足以保證之前的菊花停止轉(zhuǎn)動(dòng)熟吏。結(jié)束轉(zhuǎn)圈則會(huì)在0.17秒之后進(jìn)行,可以保證菊花的旋轉(zhuǎn)至少會(huì)有0.17秒玄窝。
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
- (void)startActivationDelayTimer {
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)startCompletionDelayTimer {
[self.completionDelayTimer invalidate];
self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
由于indicatorManager
是采用通知的方式進(jìn)行回調(diào)牵寺,所有的網(wǎng)絡(luò)請(qǐng)求通知都會(huì)調(diào)到這。所以當(dāng)多個(gè)網(wǎng)絡(luò)請(qǐng)求到來時(shí)恩脂,會(huì)通過一個(gè)_activityCount
來進(jìn)行計(jì)數(shù)帽氓,可以將其理解為一個(gè)隊(duì)列,這樣更容易理解俩块。在顯示網(wǎng)絡(luò)指示器的時(shí)候黎休,就是基于_activityCount
來進(jìn)行判斷的浓领,如果隊(duì)列中有請(qǐng)求則顯示網(wǎng)絡(luò)指示器,無論有多少請(qǐng)求奋渔。
這種設(shè)計(jì)思路比較好镊逝,在項(xiàng)目中很多地方都可以用到。例如有些方法需要成對(duì)進(jìn)行調(diào)用嫉鲸,例如播放開始和暫停撑蒜,如果某一個(gè)方法調(diào)用多次就會(huì)造成bug
。這種方式就比較適合用count
的方式進(jìn)行容錯(cuò)玄渗,內(nèi)部針對(duì)count
做一些判斷操作座菠。
- (void)incrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount = MAX(_activityCount - 1, 0);
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
indicatorManager
是多線程安全的,在一些關(guān)鍵地方都通過synchronized
的方式加鎖藤树,防止從各個(gè)線程調(diào)用過來的通知造成資源搶奪的問題浴滴。
AFSecurityPolicy
驗(yàn)證處理
AFN
支持https
請(qǐng)求,并通過AFSecurityPolicy
類來處理https
證書及驗(yàn)證岁钓,但其https
請(qǐng)求的執(zhí)行還是交給NSURLSession
去完成的升略。
下面是NSURLSession
的一個(gè)代理方法,當(dāng)需要進(jìn)行證書驗(yàn)證時(shí)屡限,可以重寫此方法并進(jìn)行自定義的驗(yàn)證處理品嚣。驗(yàn)證完成后通過completionHandler
的block
來告知處理結(jié)果,并且將驗(yàn)證結(jié)果disposition
和公鑰credential
傳入钧大。
AFN
通過AFSecurityPolicy
類提供了驗(yàn)證邏輯翰撑,并且在內(nèi)部可以進(jìn)行證書的管理。也可以不使用AFN
提供的驗(yàn)證邏輯啊央,重寫sessionDidReceiveAuthenticationChallenge
的block
即可自定義驗(yàn)證邏輯眶诈,不走AFN
的邏輯。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
除了進(jìn)行NSURLSession
請(qǐng)求驗(yàn)證的回調(diào)瓜饥,對(duì)于每個(gè)task
也有對(duì)應(yīng)的代理方法逝撬。兩個(gè)代理方法內(nèi)部實(shí)現(xiàn)基本一樣,區(qū)別在于對(duì)于每個(gè)task
乓土,AFN
提供了taskDidReceiveAuthenticationChallenge
回調(diào)block
球拦,可以由外界自定義證書驗(yàn)證過程。
驗(yàn)證結(jié)果是通過一個(gè)枚舉回調(diào)給NSURLSession
的帐我,參數(shù)是一個(gè)NSURLSessionAuthChallengeDisposition
類型的枚舉坎炼,表示證書驗(yàn)證的情況,此枚舉包含下面幾個(gè)具體值拦键。
- NSURLSessionAuthChallengeUseCredential
使用當(dāng)前證書建立SSL
連接谣光,并處理后續(xù)請(qǐng)求 - NSURLSessionAuthChallengePerformDefaultHandling
使用默認(rèn)的處理方式,當(dāng)前證書被忽略 - NSURLSessionAuthChallengeCancelAuthenticationChallenge
驗(yàn)證不通過芬为,取消整個(gè)網(wǎng)絡(luò)請(qǐng)求 - NSURLSessionAuthChallengeRejectProtectionSpace
這次驗(yàn)證被忽略萄金,但不取消網(wǎng)絡(luò)請(qǐng)求
Security
HTTPS
請(qǐng)求的密鑰管理等安全相關(guān)的處理蟀悦,都放在Security.framework
框架中。在AFSecurityPolicy
中經(jīng)逞醺遥可以看到SecTrustRef
類型的變量日戈,其表示的就是密鑰對(duì)象,其中包含了公鑰等信息孙乖。
我們可以通過下面的命令獲取到公鑰浙炼,具體格式這里不做過多介紹,詳細(xì)的可以Google一下公鑰格式唯袄。
// 獲取公鑰命令
SecTrustCopyPublicKey(serverTrust)
// 打印的公鑰(公鑰已做脫敏)
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442DFBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43608D6F494360FFDEE83AD1DCE120D6F1, addr: 0x280396dc0>
AFSecurityPolicy概述
AFSecurityPolicy
的職責(zé)比較單一弯屈,只處理公鑰和驗(yàn)證的邏輯,其定義是一個(gè)單例對(duì)象恋拷。此類主要由四個(gè)屬性和一個(gè)方法構(gòu)成资厉。
// 證書驗(yàn)證方式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 本地自簽名證書集合
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否驗(yàn)證證書的合法性(是否允許自簽名證書)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否驗(yàn)證域名是否有效
@property (nonatomic, assign) BOOL validatesDomainName;
如果進(jìn)行細(xì)分的話,AFSecurityPolicy
的功能基本就兩個(gè)蔬顾。一個(gè)是通過CA
的方式進(jìn)行驗(yàn)證宴偿,另一個(gè)就是進(jìn)行SSL Pinning
自簽名驗(yàn)證。evaluateServerTrust:forDomain:
是AFSecurityPolicy
最主要的方法诀豁,用來進(jìn)行證書的合法性驗(yàn)證窄刘。
SSL Pinning
AFSecurityPolicy
進(jìn)行SSL Pinning
驗(yàn)證的方式分為以下三種,如果是None
則會(huì)執(zhí)行正常CA
驗(yàn)證的流程且叁,其他兩種都是自簽名的流程。AFN
中默認(rèn)的調(diào)用是defaultPolicy
方法秩伞,其內(nèi)部設(shè)置的是AFSSLPinningModeNone
模式逞带。
- AFSSLPinningModeNone
正常流程,通過CA
機(jī)構(gòu)頒發(fā)的公鑰纱新,對(duì)服務(wù)器下發(fā)的證書驗(yàn)證數(shù)字簽名展氓,并且獲得公鑰。 - AFSSLPinningModeCertificate
不通過CA
的流程進(jìn)行驗(yàn)證脸爱,而是通過本地內(nèi)置的服務(wù)端證書進(jìn)行驗(yàn)證遇汞,驗(yàn)證過程分為兩步。首先驗(yàn)證證書是否過期或失效簿废,其次驗(yàn)證本地是否包含此證書空入。 - AFSSLPinningModePublicKey
不進(jìn)行CA
的驗(yàn)證,也不驗(yàn)證證書族檬,只驗(yàn)證公鑰是否有效歪赢。
對(duì)于本地自簽名證書的管理有兩種方式,一種是默認(rèn)會(huì)在本地查找遍歷所有.cer
的文件单料,并存在一個(gè)自簽名證書的集合中埋凯。也可以在創(chuàng)建AFSecurityPolicy
對(duì)象時(shí)傳入SSLPinningMode
点楼,下面是查找本地.cer
文件的邏輯。
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
return [NSSet setWithSet:certificates];
}
自簽名證書
HTTPS
在進(jìn)行握手時(shí)白对,需要通過CA
的公鑰進(jìn)行驗(yàn)證掠廓,保證服務(wù)器公鑰的合法性,沒有被篡改為有問題的公鑰甩恼。如果使用CA
機(jī)構(gòu)頒發(fā)的證書蟀瞧,無論使用NSURLSession
還是AFNetworking
都不需要修改代碼,這些都會(huì)自動(dòng)完成媳拴。如果不想使用CA
的證書驗(yàn)證黄橘,例如自簽名證書在CA
證書驗(yàn)證時(shí)就會(huì)失敗。
這種情況可以使用自簽名的方式進(jìn)行驗(yàn)證屈溉,也就是在客戶端本地內(nèi)置一份證書塞关,服務(wù)器進(jìn)行四次握手時(shí),通過保存在本地的證書和服務(wù)器進(jìn)行對(duì)比子巾,證書相同則表示驗(yàn)證成功帆赢,不走CA
的驗(yàn)證方式。
AFN
為我們提供了自簽名證書的驗(yàn)證方法线梗,通過SSLPinningMode
設(shè)置驗(yàn)證方式為自簽名椰于,并且傳入證書集合。如果沒有傳入證書集合仪搔,則AFN
默認(rèn)會(huì)遍歷整個(gè)沙盒瘾婿,查找所有.cer
的證書。
進(jìn)行沙盒驗(yàn)證時(shí)烤咧,需要將AFSecurityPolicy
的allowInvalidCertificates
設(shè)置為YES
偏陪,默認(rèn)是NO
,表示允許無效的證書煮嫌,也就是自簽名的證書笛谦。
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
securityPolicy.allowInvalidCertificates = YES;
AFNetworking 2.x
AFNetworking2.x
由NSURLSession
和NSURLConnection
兩部分組成,并且分別對(duì)應(yīng)不同的類昌阿,這里主要介紹NSURLConnection
部分的源碼實(shí)現(xiàn)饥脑。
總體概覽
NSURLConnection
實(shí)現(xiàn)由下面三個(gè)類構(gòu)成,從源碼構(gòu)成可以看出懦冰,無論是session
還是connection
方案灶轰,都具有很好的擴(kuò)展性。例如這里AFHTTPRequestOperation
是基于AFURLConnectionOperation
實(shí)現(xiàn)的刷钢,如果需要實(shí)現(xiàn)FTP
協(xié)議框往,則可以創(chuàng)建一個(gè)繼承自AFURLConnectionOperation
的AFFPTConnectionOperation
類并重寫對(duì)應(yīng)方法即可。
- AFURLConnectionOperation
繼承自NSOperation
闯捎,負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求的邏輯實(shí)現(xiàn)椰弊,每個(gè)網(wǎng)絡(luò)請(qǐng)求就是一個(gè)Operation
對(duì)象许溅。 - AFHTTPRequestOperation
繼承自AFURLConnectionOperation
,處理HTTP
相關(guān)網(wǎng)絡(luò)請(qǐng)求秉版。 - AFHTTPRequestOperationManager
內(nèi)部持有一個(gè)NSOperationQueue
贤重,負(fù)責(zé)管理所有Operation
網(wǎng)絡(luò)請(qǐng)求。
AFURLConnectionOperation
下面是AFURLConnectionOperation
的初始化方法清焕,和AFURLSessionManager
有些不一樣并蝗。其內(nèi)部增加了狀態(tài)的概念,以及RunloopMode
的概念秸妥,這兩個(gè)我們后面會(huì)詳細(xì)講解滚停。shouldUseCredentialStorage
表示是否由系統(tǒng)做證書驗(yàn)證,后面設(shè)置了securityPolicy
粥惧,和sessionManager
一樣也是使用默認(rèn)方案键畴。
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
_state = AFOperationReadyState;
self.lock = [[NSRecursiveLock alloc] init];
self.lock.name = kAFNetworkingLockName;
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.request = urlRequest;
self.shouldUseCredentialStorage = YES;
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
}
AFURLConnectionOperation
繼承自NSOperation
,由于NSOperation
和網(wǎng)絡(luò)請(qǐng)求的過程很像突雪,有開始起惕、暫停、完成等咏删,并且很好的支持KVO
監(jiān)聽惹想,所以AFN
將每個(gè)網(wǎng)絡(luò)請(qǐng)求都當(dāng)做一個(gè)Operation
任務(wù)。AFURLConnectionOperation
可以設(shè)置任務(wù)優(yōu)先級(jí)督函,也可以通過AFHTTPRequestOperationManager
設(shè)置最大并發(fā)數(shù)嘀粱,基本上NSOperationQueue
提供的功能都能用。
在AFHTTPRequestOperationManager
中定義了NSOperationQueue
辰狡,創(chuàng)建網(wǎng)絡(luò)請(qǐng)求任務(wù)后锋叨,都會(huì)被加入到queue
中,隨后由系統(tǒng)調(diào)用queue
中的operation
任務(wù)搓译,執(zhí)行operation
的start
方法發(fā)起請(qǐng)求悲柱。AFURLConnectionOperation
只需要在內(nèi)部實(shí)現(xiàn)start
锋喜、pause
些己、resume
等父類方法即可,其他都由系統(tǒng)去進(jìn)行調(diào)用嘿般。這種設(shè)計(jì)可以很好的將manager
和operation
進(jìn)行解耦段标,二者不用直接發(fā)生調(diào)用關(guān)系。
NSURLConnection
中炉奴,每個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)一個(gè)AFHTTPRequestOperation
逼庞,所有網(wǎng)絡(luò)請(qǐng)求都共用一個(gè)manager
來管理operation
。而AFHTTPSessionManager
則不同瞻赶,每個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)一個(gè)manager
以及一個(gè)task
赛糟。
state設(shè)計(jì)
AFURLConnectionOperation
支持KVO
的方式派任,讓外界監(jiān)聽網(wǎng)絡(luò)請(qǐng)求的變化,并通過重寫setState
方法璧南,在內(nèi)部加入willChangeValueForKey
觸發(fā)KVO
回調(diào)掌逛。AFN
通過AFOperationState
來管理網(wǎng)絡(luò)請(qǐng)求狀態(tài),下面是AFN
對(duì)其的狀態(tài)定義司倚。
- AFOperationPausedState
請(qǐng)求暫停 - AFOperationReadyState
請(qǐng)求已準(zhǔn)備好 - AFOperationExecutingState
請(qǐng)求正在執(zhí)行中 - AFOperationFinishedState
請(qǐng)求完成
當(dāng)網(wǎng)絡(luò)請(qǐng)求狀態(tài)發(fā)生改變時(shí)豆混,都會(huì)調(diào)用setState
方法進(jìn)行賦值,例如下面是請(qǐng)求完成時(shí)的處理代碼动知。除此之外皿伺,當(dāng)判斷AFN
請(qǐng)求狀態(tài)時(shí),也是通過這個(gè)屬性作為判斷依據(jù)的盒粮。
- (void)finish {
[self.lock lock];
self.state = AFOperationFinishedState;
[self.lock unlock];
}
常駐線程
AFURLConnectionOperation
中設(shè)計(jì)了常駐線程鸵鸥,并且重寫了operation
的start
等方法,網(wǎng)絡(luò)請(qǐng)求的start
拆讯、cancel
脂男、pause
等操作,都是在常駐線程中完成的种呐。網(wǎng)絡(luò)請(qǐng)求結(jié)束后嗅榕,數(shù)據(jù)回調(diào)的處理也是在這個(gè)線程中完成的。
這是因?yàn)樵谀膫€(gè)線程創(chuàng)建NSURLConnection
對(duì)象并發(fā)出請(qǐng)求斋枢,則數(shù)據(jù)返回時(shí)也默認(rèn)從那個(gè)線程接受數(shù)據(jù)已维。如果請(qǐng)求都是從主線程發(fā)出的,請(qǐng)求返回時(shí)如果屏幕正在滑動(dòng)阔墩,runloopMode
為UITrackingRunLoopMode
則不能處理返回?cái)?shù)據(jù)嘿架。而如果把網(wǎng)絡(luò)請(qǐng)求都加到主線程的NSRunLoopCommonModes
中,在大量網(wǎng)絡(luò)請(qǐng)求返回時(shí)啸箫,處理返回?cái)?shù)據(jù)會(huì)影響屏幕滑動(dòng)FPS
耸彪。
所以為了保證網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)可以正常返回并被處理,而又不影響屏幕FPS
忘苛,則用一個(gè)單獨(dú)的線程來處理蝉娜。如果每個(gè)請(qǐng)求都對(duì)應(yīng)一個(gè)線程來處理返回任務(wù),會(huì)造成大量線程的占用扎唾,所以用一個(gè)常駐線程來處理所有網(wǎng)絡(luò)請(qǐng)求召川,來保證線程資源的最小占用。常駐線程實(shí)際上是一個(gè)單例線程胸遇,并且這個(gè)單例線程被加入了一個(gè)Port
進(jìn)行庇牛活,保證線程可以不被退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
通過AFURLConnectionOperation
發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí)倍阐,實(shí)際創(chuàng)建connection
對(duì)象的代碼在下面的方法中概疆。在創(chuàng)建connection
對(duì)象后,并沒有立即發(fā)出網(wǎng)絡(luò)請(qǐng)求峰搪,而是將startImmediately
設(shè)置為NO
届案。隨后會(huì)設(shè)置NSURLConnection
和NSOutputStream
的RunloopMode
,網(wǎng)絡(luò)請(qǐng)求會(huì)從單例線程的runLoopModes
中發(fā)出罢艾,這樣當(dāng)網(wǎng)絡(luò)請(qǐng)求返回時(shí)楣颠,回調(diào)代碼也會(huì)在runLoopModes
中去執(zhí)行。
operationDidStart
方法中會(huì)調(diào)用NSURLConnection
的scheduleInRunLoop:forMode:
方法咐蚯,將網(wǎng)絡(luò)請(qǐng)求任務(wù)派發(fā)到Runloop
指定的Mode
中童漩。我覺得給Operation
設(shè)置runLoopModes
其實(shí)意義不大,因?yàn)槌qv線程基本上只會(huì)有一個(gè)Mode
春锋,也就是NSRunloopDefaultMode
矫膨,基本上不會(huì)有其他Mode
,所以這里設(shè)置runLoopModes
沒什么意義期奔。
- (void)operationDidStart {
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for (NSString *runLoopMode in self.runLoopModes) {
[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
}
[self.outputStream open];
[self.connection start];
}
代理方法
AFURLConnectionOperation
是通過NSURLConnection
實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的侧馅,這里簡(jiǎn)單講一下operation
中代理方法的實(shí)現(xiàn)。
AFN
實(shí)現(xiàn)了https
證書驗(yàn)證的代碼呐萌,具體實(shí)現(xiàn)和AFURLSessionManager
基本類似馁痴,并且也是通過AFSecurityPolicy
來處理具體的證書驗(yàn)證邏輯。
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
關(guān)于請(qǐng)求服務(wù)器數(shù)據(jù)這塊值得講一下肺孤,在NSURLConnection
接收服務(wù)器數(shù)據(jù)時(shí)罗晕,AFN
通過創(chuàng)建了一個(gè)outputStream
,來承載和組織具體的數(shù)據(jù)赠堵,并且在內(nèi)存中進(jìn)行存儲(chǔ)小渊。當(dāng)沒有可用空間或發(fā)生其他錯(cuò)誤時(shí),會(huì)通過streamError
的方式進(jìn)行體現(xiàn)茫叭。
當(dāng)網(wǎng)絡(luò)請(qǐng)求結(jié)束時(shí)酬屉,會(huì)調(diào)用didFinishLoading
方法,AFN
會(huì)從outputStream
中拿出數(shù)據(jù)并賦值給responseData
揍愁,當(dāng)做返回值數(shù)據(jù)使用呐萨。
- (void)connection:(NSURLConnection __unused *)connection
didReceiveData:(NSData *)data
{
NSUInteger length = [data length];
while (YES) {
NSInteger totalNumberOfBytesWritten = 0;
if ([self.outputStream hasSpaceAvailable]) {
const uint8_t *dataBuffer = (uint8_t *)[data bytes];
NSInteger numberOfBytesWritten = 0;
while (totalNumberOfBytesWritten < (NSInteger)length) {
numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
if (numberOfBytesWritten == -1) {
break;
}
totalNumberOfBytesWritten += numberOfBytesWritten;
}
break;
} else {
[self.connection cancel];
if (self.outputStream.streamError) {
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
}
return;
}
}
if (self.downloadProgress) {
self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
}
}
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
[self.outputStream close];
if (self.responseData) {
self.outputStream = nil;
}
self.connection = nil;
[self finish];
}
有outputStream
,也會(huì)有與之對(duì)應(yīng)的inputStream
吗垮,inputStream
實(shí)現(xiàn)很簡(jiǎn)單垛吗,就是修改NSMutableURLRequest
的HTTPBodyStream
凹髓。
- (NSInputStream *)inputStream {
return self.request.HTTPBodyStream;
}
- (void)setInputStream:(NSInputStream *)inputStream {
NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
mutableRequest.HTTPBodyStream = inputStream;
self.request = mutableRequest;
}
內(nèi)存管理
在創(chuàng)建AFHTTPRequestOperation
時(shí)會(huì)將success
和failure
的block
傳給operation
烁登,并且在operation
執(zhí)行完成并回調(diào)completionBlock
時(shí),執(zhí)行這兩個(gè)block
代碼。但是由于completionBlock
中直接使用了self
饵沧,導(dǎo)致了循環(huán)引用的問題锨络。
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
self.completionBlock = ^{
// something code ...
};
}
completionBlock
的循環(huán)引用是AFN
有意而為之的,為的就是保持operation
的生命周期狼牺,以保證請(qǐng)求處理完成并接收返回的block
回調(diào)羡儿。
對(duì)于循環(huán)引用的生命周期,AFN
采取的是主動(dòng)打破循環(huán)引用的方式是钥,也就是重寫父類的completionBlock
掠归,并且在調(diào)用block
結(jié)束后,主動(dòng)將completionBlock
賦值為nil
悄泥,從而主動(dòng)打破循環(huán)引用虏冻。
- (void)setCompletionBlock:(void (^)(void))block {
if (!block) {
[super setCompletionBlock:nil];
} else {
__weak __typeof(self)weakSelf = self;
[super setCompletionBlock:^ {
__strong __typeof(weakSelf)strongSelf = weakSelf;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
dispatch_group_async(group, queue, ^{
block();
});
dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
[strongSelf setCompletionBlock:nil];
});
}];
}
}
AFNetworkReachabilityManager
AFNetworking
中還有很重要的一部分,就是Reachability
弹囚,用來做網(wǎng)絡(luò)狀態(tài)監(jiān)控的厨相。AFNetworking
、YYKit
鸥鹉、蘋果官方都提供有Reachability
的API
使用蛮穿,內(nèi)部實(shí)現(xiàn)原理基本差不多。
代碼實(shí)現(xiàn)也很簡(jiǎn)單毁渗,主要依賴SystemConfiguration.framework
框架的SCNetworkReachability
践磅,注冊(cè)一個(gè)Callback
然后等著回調(diào)就可以。這里講一下核心邏輯灸异,一些細(xì)枝末節(jié)的就忽略了音诈。
Reachability
提供了兩種初始化方法,一種是通過域名初始化的managerForDomain:
方法绎狭,傳入一個(gè)域名细溅,基于這個(gè)域名的訪問情況來判斷當(dāng)前網(wǎng)絡(luò)狀態(tài)。另一種是通過地址初始化的managerForAddress:
方法儡嘶,創(chuàng)建一個(gè)sockaddr_in
對(duì)象喇聊,并基于這個(gè)對(duì)象來判斷網(wǎng)絡(luò)狀態(tài)。
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
+ (instancetype)manager {
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
return [self managerForAddress:&address];
}
下面startMonitoring
中是開啟網(wǎng)絡(luò)監(jiān)測(cè)的核心代碼蹦狂,主要邏輯是設(shè)置了兩個(gè)Callback
誓篱,一個(gè)是block
的一個(gè)是函數(shù)的,并添加到Runloop
中開始監(jiān)控凯楔。由此可以推測(cè)窜骄,Reachability
的代碼實(shí)現(xiàn)主要依賴Runloop
的事件循環(huán),并且在事件循環(huán)中判斷網(wǎng)絡(luò)狀態(tài)摆屯。
當(dāng)網(wǎng)絡(luò)發(fā)生改變時(shí)邻遏,就會(huì)回調(diào)AFNetworkReachabilityCallback
函數(shù),回調(diào)有三個(gè)參數(shù)。target
是SCNetworkReachabilityRef
對(duì)象准验,flags
是網(wǎng)絡(luò)狀態(tài)赎线,info
是我們?cè)O(shè)置的block
回調(diào)參數(shù)『ィ回調(diào)Callback
函數(shù)后垂寥,內(nèi)部會(huì)通過block
以及通知的形式,對(duì)外發(fā)出回調(diào)另锋。
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
AFNetworking總結(jié)
AFNetworking
對(duì)請(qǐng)求數(shù)據(jù)的序列化滞项,以及返回?cái)?shù)據(jù)的反序列化做了很多處理。使開發(fā)者只需要傳入一個(gè)字典即可構(gòu)建請(qǐng)求參數(shù)夭坪,無需處理拼接到URL
后面或參數(shù)轉(zhuǎn)換為body
二進(jìn)制的細(xì)節(jié)蓖扑,這些都由AFNetworking
內(nèi)部處理并進(jìn)行容錯(cuò),開發(fā)者只需要指定請(qǐng)求方式即可台舱。
通過AFNetworking
實(shí)現(xiàn)https
也很方便律杠,AFSecurityPolicy
可以很好的管理CA
以及自簽名證書,以及處理https
請(qǐng)求過程中的一些邏輯竞惋,我們只需要告訴AFNetworking
怎么處理即可柜去,如果不指定處理方式則使用默認(rèn)CA
證書的方式處理。
AFNetworking
對(duì)于后臺(tái)下載以及斷點(diǎn)續(xù)傳有很好的支持拆宛,我們可以在AFNetworking
的基礎(chǔ)上嗓奢,很簡(jiǎn)單的就完成一個(gè)下載模塊的設(shè)計(jì)。如果自己寫后臺(tái)下載和斷點(diǎn)續(xù)傳的代碼浑厚,工作量還是不小的股耽。
并且AFNetworking
在網(wǎng)絡(luò)庫的設(shè)計(jì)上還提供了很強(qiáng)的自定義性,例如指定證書钳幅、URL
緩存處理物蝙,以及下載過程中不同下載階段的處理。如果沒有提供自定義處理敢艰,則使用默認(rèn)處理方式诬乞。