1.整體架構(gòu)
2.核心類(lèi) AFURLSessionManager
2.1.初始化方法 init
self.operationQueue = [[NSOperationQueue alloc] init];
//queue并發(fā)線(xiàn)程數(shù)設(shè)置為1
self.operationQueue.maxConcurrentOperationCount = 1;
1)首先我們要明確一個(gè)概念摩疑,這里的并發(fā)數(shù)僅僅是回調(diào)代理的線(xiàn)程并發(fā)數(shù)凶赁。而不是請(qǐng)求網(wǎng)絡(luò)的線(xiàn)程并發(fā)數(shù)壕探。請(qǐng)求網(wǎng)絡(luò)是由NSUrlSession來(lái)做的琅催,它內(nèi)部維護(hù)了一個(gè)線(xiàn)程池圆丹,用來(lái)做網(wǎng)絡(luò)請(qǐng)求滩愁。它調(diào)度線(xiàn)程,基于底層的CFSocket去發(fā)送請(qǐng)求和接收數(shù)據(jù)。這些線(xiàn)程是并發(fā)的辫封。
2)明確了這個(gè)概念之后硝枉,我們來(lái)梳理一下AF3.x的整個(gè)流程和線(xiàn)程的關(guān)系:
我們一開(kāi)始初始化sessionManager
的時(shí)候,一般都是在主線(xiàn)程倦微,(當(dāng)然不排除有些人喜歡在分線(xiàn)程初始化...)
- 然后我們調(diào)用get
或者post
等去請(qǐng)求數(shù)據(jù)妻味,接著會(huì)進(jìn)行request
拼接,AF代理的字典映射欣福,progress
的KVO
添加等等责球,到NSUrlSession
的resume
之前這些準(zhǔn)備工作,仍舊是在主線(xiàn)程中的拓劝。- 然后我們調(diào)用NSUrlSession
的resume
雏逾,接著就跑到NSUrlSession
內(nèi)部去對(duì)網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)請(qǐng)求了,在它內(nèi)部是多線(xiàn)程并發(fā)的去請(qǐng)求數(shù)據(jù)的。- 緊接著數(shù)據(jù)請(qǐng)求完成后郑临,回調(diào)回來(lái)在我們一開(kāi)始生成的并發(fā)數(shù)為1的NSOperationQueue
中栖博,這個(gè)時(shí)候會(huì)是多線(xiàn)程串行的回調(diào)回來(lái)的。- 然后我們到返回?cái)?shù)據(jù)解析那一塊牧抵,我們自己又創(chuàng)建了并發(fā)的多線(xiàn)程笛匙,去對(duì)這些數(shù)據(jù)進(jìn)行了各種類(lèi)型的解析侨把。
最后我們?nèi)绻凶远x的completionQueue
,則在自定義的queue
中回調(diào)回來(lái)妹孙,也就是分線(xiàn)程回調(diào)回來(lái)秋柄,否則就是主隊(duì)列,主線(xiàn)程中回調(diào)結(jié)束蠢正。3)最后我們來(lái)解釋解釋為什么回調(diào)Queue要設(shè)置并發(fā)數(shù)為1:
我認(rèn)為AF這么做有以下兩點(diǎn)原因:
1)眾所周知骇笔,AF2.x所有的回調(diào)是在一條線(xiàn)程,這條線(xiàn)程是AF的常駐線(xiàn)程嚣崭,而這一條線(xiàn)程正是AF調(diào)度request的思想精髓所在笨触,所以第一個(gè)目的就是為了和之前版本保持一致。
2)因?yàn)楦硐嚓P(guān)的一些操作AF都使用了NSLock雹舀。所以就算Queue的并發(fā)數(shù)設(shè)置為n芦劣,因?yàn)槎嗑€(xiàn)程回調(diào),鎖的等待说榆,導(dǎo)致所提升的程序速度也并不明顯虚吟。反而多task回調(diào)導(dǎo)致的多線(xiàn)程并發(fā),平白浪費(fèi)了部分性能签财。而設(shè)置Queue的并發(fā)數(shù)為1串慰,(注:這里雖然回調(diào)Queue的并發(fā)數(shù)為1,仍然會(huì)有不止一條線(xiàn)程唱蒸,但是因?yàn)槭谴谢卣{(diào)邦鲫,所以同一時(shí)間,只會(huì)有一條線(xiàn)程在操作AFUrlSessionManager的那些方法神汹。)至少回調(diào)的事件庆捺,是不需要多線(xiàn)程并發(fā)的。回調(diào)沒(méi)有了NSLock的等待時(shí)間慎冤,所以對(duì)時(shí)間并沒(méi)有多大的影響疼燥。(注:但是還是會(huì)有多線(xiàn)程的操作的,因?yàn)樵O(shè)置剛開(kāi)始調(diào)起請(qǐng)求的時(shí)候蚁堤,是在主線(xiàn)程的醉者,而回調(diào)則是串行分線(xiàn)程。)
// 設(shè)置存儲(chǔ)NSURL task與AFURLSessionManagerTaskDelegate的詞典(重點(diǎn)披诗,在AFNet中撬即,每一個(gè)task都會(huì)被匹配一個(gè)AFURLSessionManagerTaskDelegate 來(lái)做task的delegate事件處理)
這個(gè)是用來(lái)讓每一個(gè)請(qǐng)求task和我們自定義的AF代理來(lái)建立映射用的,其實(shí)AF對(duì)task的代理進(jìn)行了一個(gè)封裝呈队,并且轉(zhuǎn)發(fā)代理到AF自定義的代理
===============
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
2.2.生成request中剥槐,參數(shù)序列化
AFQueryStringFromParameters
@{
@"name" : @"bang",
@"phone": @{@"mobile": @"xx", @"home": @"xx"},
@"families": @[@"father", @"mother"],
@"nums": [NSSet setWithObjects:@"1", @"2", nil]
}
->
@[
field: @"name", value: @"bang",
field: @"phone[mobile]", value: @"xx",
field: @"phone[home]", value: @"xx",
field: @"families[]", value: @"father",
field: @"families[]", value: @"mother",
field: @"nums", value: @"1",
field: @"nums", value: @"2",
]
->
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
緊接著這個(gè)方法還根據(jù)該request中請(qǐng)求類(lèi)型,來(lái)判斷參數(shù)字符串應(yīng)該如何設(shè)置到request中去宪摧。如果是GET粒竖、HEAD颅崩、DELETE,則把參數(shù)quey是拼接到url后面的蕊苗。而POST沿后、PUT是把query拼接到http body中的:
2.3.請(qǐng)求數(shù)據(jù)成功后可以不回調(diào)在主線(xiàn)程
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;
}
當(dāng)解析錯(cuò)誤朽砰,我們直接調(diào)用傳進(jìn)來(lái)的fauler的Block失敗返回了尖滚,這里有一個(gè)self.completionQueue,這個(gè)是我們自定義的,這個(gè)是一個(gè)GCD的Queue如果設(shè)置了那么從這個(gè)Queue中回調(diào)結(jié)果瞧柔,否則從主隊(duì)列回調(diào)漆弄。
實(shí)際上這個(gè)Queue還是挺有用的,之前還用到過(guò)造锅。我們公司有自己的一套數(shù)據(jù)加解密的解析模式撼唾,所以我們回調(diào)回來(lái)的數(shù)據(jù)并不想是主線(xiàn)程,我們可以設(shè)置這個(gè)Queue,在分線(xiàn)程進(jìn)行解析數(shù)據(jù)备绽,然后自己再調(diào)回到主線(xiàn)程去刷新UI券坞。
2.4.處理 task
- (NSURLSessionDataTask *)dataTaskWithRequest....
//第一件事,創(chuàng)建NSURLSessionDataTask肺素,里面適配了Ios8以下taskIdentifiers,函數(shù)創(chuàng)建task對(duì)象宇驾。
//其實(shí)現(xiàn)應(yīng)該是因?yàn)閕OS 8.0以下版本中會(huì)并發(fā)地創(chuàng)建多個(gè)task對(duì)象倍靡,而同步有沒(méi)有做好,導(dǎo)致taskIdentifiers 不唯一…這邊做了一個(gè)串行處理
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
url_session_manager_create_task_safely
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
//理解下课舍,第一為什么用sync塌西,因?yàn)槭窍胍骶€(xiàn)程等在這,等執(zhí)行完筝尾,在返回捡需,因?yàn)楸仨殘?zhí)行完dataTask才有數(shù)據(jù),傳值才有意義筹淫。
//第二站辉,為什么要用串行隊(duì)列,因?yàn)檫@塊是為了防止ios8以下內(nèi)部的dataTaskWithRequest是并發(fā)創(chuàng)建的损姜,
//這樣會(huì)導(dǎo)致taskIdentifiers這個(gè)屬性值不唯一饰剥,因?yàn)楹罄m(xù)要用taskIdentifiers來(lái)作為Key對(duì)應(yīng)delegate。
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
//保證了即使是在多線(xiàn)程的環(huán)境下摧阅,也不會(huì)創(chuàng)建其他隊(duì)列
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;
}
原來(lái)這是為了適配iOS8的以下汰蓉,創(chuàng)建session的時(shí)候,偶發(fā)的情況會(huì)出現(xiàn)session的屬性taskIdentifier這個(gè)值不唯一棒卷,而這個(gè)taskIdentifier是我們后面來(lái)映射delegate的key,所以它必須是唯一的顾孽。
具體原因應(yīng)該是NSURLSession內(nèi)部去生成task的時(shí)候是用多線(xiàn)程并發(fā)去執(zhí)行的祝钢。
2.5.- (void)setDelegate:
//斷言,如果沒(méi)有這個(gè)參數(shù)若厚,debug下crash在這
NSParameterAssert(task);
NSParameterAssert(delegate);
//加鎖保證字典線(xiàn)程安全
[self.lock lock];
// 將AF delegate放入以taskIdentifier標(biāo)記的詞典中(同一個(gè)NSURLSession中的taskIdentifier是唯一的)
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 為AF delegate 設(shè)置task 的progress監(jiān)聽(tīng)
[delegate setupProgressForTask:task];
//添加task開(kāi)始和暫停的通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
加鎖的原因是因?yàn)楸旧砦覀冞@個(gè)字典屬性是mutable的拦英,是線(xiàn)程不安全的。而我們對(duì)這些方法的調(diào)用盹沈,確實(shí)是會(huì)在復(fù)雜的多線(xiàn)程環(huán)境中
之前的setProgress和這個(gè)KVO監(jiān)聽(tīng)龄章,都是在我們AF自定義的delegate內(nèi)的,是有一個(gè)task就會(huì)有一個(gè)delegate的乞封。所以說(shuō)我們是每個(gè)task都會(huì)去監(jiān)聽(tīng)這些屬性做裙,分別在各自的AF代理內(nèi)
2.6.session 代理
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
我們把AFUrlSessionManager作為了所有的task的delegate。當(dāng)我們請(qǐng)求網(wǎng)絡(luò)的時(shí)候肃晚,這些代理開(kāi)始調(diào)用了
而只轉(zhuǎn)發(fā)了其中3條到AF自定義的delegate中:
AFUrlSessionManager對(duì)這一大堆代理做了一些公共的處理锚贱,而轉(zhuǎn)發(fā)到AF自定義代理的3條,則負(fù)責(zé)把每個(gè)task對(duì)應(yīng)的數(shù)據(jù)回調(diào)出去关串。
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
各自對(duì)應(yīng)這樣的set方法
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
self.sessionDidBecomeInvalid = block;
}
設(shè)計(jì)思路:
1.作者用@property把這個(gè)些Block屬性在.m文件中聲明,然后復(fù)寫(xiě)了set方法拧廊。
2.然后在.h中去聲明這些set方法:
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
為什么要繞這么一大圈呢?原來(lái)這是為了我們這些用戶(hù)使用起來(lái)方便晋修,調(diào)用set方法去設(shè)置這些Block吧碾,能很清晰的看到Block的各個(gè)參數(shù)與返回值。
3.NSURLSessionDelegate
1.每個(gè)代理方法對(duì)應(yīng)一個(gè)我們自定義的Block,如果Block被賦值了墓卦,那么就調(diào)用它倦春。
2.在這些代理方法里,我們做的處理都是相對(duì)于這個(gè)sessionManager所有的request的落剪。是公用的處理睁本。
3.轉(zhuǎn)發(fā)了3個(gè)代理方法到AF的deleagate中去了,AF中的deleagate是需要對(duì)應(yīng)每個(gè)task去私有化處理的忠怖。
1.當(dāng)前這個(gè)session已經(jīng)失效時(shí)呢堰,該代理方法被調(diào)用。
/*
如果你使用finishTasksAndInvalidate函數(shù)使該session失效凡泣,
那么session首先會(huì)先完成最后一個(gè)task枉疼,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法,
如果你調(diào)用invalidateAndCancel方法來(lái)使session失效问麸,那么該session會(huì)立即調(diào)用上面的代理方法往衷。
*/
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
2.https認(rèn)證
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
15.用來(lái)做斷點(diǎn)續(xù)傳的代理方法
//當(dāng)下載被取消或者失敗后重新恢復(fù)下載時(shí)調(diào)用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
這3個(gè)方法,被轉(zhuǎn)調(diào)到AF自定義delegate中
8. didCompleteWithError
/*
task完成之后的回調(diào)严卖,成功和失敗都會(huì)回調(diào)這里
函數(shù)討論:
注意這里的error不會(huì)報(bào)告服務(wù)期端的error席舍,他表示的是客戶(hù)端這邊的eroor,比如無(wú)法解析hostname或者連不上host主機(jī)哮笆。
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
11. didReceiveData
//當(dāng)我們獲取到數(shù)據(jù)就會(huì)調(diào)用来颤,會(huì)被反復(fù)調(diào)用汰扭,請(qǐng)求到的數(shù)據(jù)就在這被拼裝完整
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
13. NSURLSessionDownloadDelegate
//下載完成的時(shí)候調(diào)用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
NSURLSessionConfiguration
NSURLSession對(duì)象的初始化需要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三個(gè)類(lèi)工廠(chǎng)方法:
+defaultSessionConfiguration 返回一個(gè)標(biāo)準(zhǔn)的 configuration福铅,這個(gè)配置實(shí)際上與 NSURLConnection 的網(wǎng)絡(luò)堆棧(networking stack)是一樣的萝毛,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage滑黔。
+ephemeralSessionConfiguration 返回一個(gè)預(yù)設(shè)配置笆包,這個(gè)配置中不會(huì)對(duì)緩存,Cookie 和證書(shū)進(jìn)行持久性的存儲(chǔ)略荡。這對(duì)于實(shí)現(xiàn)像秘密瀏覽這種功能來(lái)說(shuō)是很理想的庵佣。
+backgroundSessionConfiguration:(NSString *)identifier 的獨(dú)特之處在于,它會(huì)創(chuàng)建一個(gè)后臺(tái) session汛兜。后臺(tái) session 不同于常規(guī)的巴粪,普通的 session,它甚至可以在應(yīng)用程序掛起粥谬,退出或者崩潰的情況下運(yùn)行上傳和下載任務(wù)肛根。初始化時(shí)指定的標(biāo)識(shí)符,被用于向任何可能在進(jìn)程外恢復(fù)后臺(tái)傳輸?shù)氖刈o(hù)進(jìn)程(daemon)提供上下文漏策。
4. AFHTTPResponseSerializer 解析數(shù)據(jù)
// 判斷是不是可接受類(lèi)型和可接受code派哲,不是則填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
可接受類(lèi)型 self.acceptableContentTypes
可接受code self.acceptableStatusCodes
5. _AFURLSessionTaskSwizzling
在AFURLSessionManager中,有這么一個(gè)類(lèi):_AFURLSessionTaskSwizzling掺喻。這個(gè)類(lèi)大概的作用就是替換掉NSUrlSession中的resume和suspend方法狮辽。正常處理原有邏輯的同時(shí),多發(fā)送一個(gè)通知
AFNSURLSessionTaskDidResumeNotification
AFNSURLSessionTaskDidSuspendNotification
這是因?yàn)閕OS7和iOS8的NSURLSessionTask的繼承鏈不同導(dǎo)致的
目前我們所知的:
- NSURLSessionTasks是一組class的統(tǒng)稱(chēng)巢寡,如果你僅僅使用提供的API來(lái)獲取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類(lèi)型的變量谦絮,為了獲取對(duì)應(yīng)Class)的類(lèi)型是 NSCFLocalDataTask,NSCFLocalDataTask繼承自NSCFLocalSessionTask洁仗,NSCFLocalSessionTask繼承自__NSCFURLSessionTask层皱。
- iOS 8上,localDataTask的類(lèi)型為NSCFLocalDataTask赠潦,NSCFLocalDataTask繼承自NSCFLocalSessionTask叫胖,NSCFLocalSessionTask繼承自NSURLSessionTask
- iOS 7上,NSCFLocalSessionTask和NSCFURLSessionTask是僅有的兩個(gè)實(shí)現(xiàn)了resume和suspend方法的類(lèi)她奥,另外NSCFLocalSessionTask中的resume和suspend并沒(méi)有調(diào)用其父類(lèi)(即NSCFURLSessionTask)方法瓮增,這也意味著兩個(gè)類(lèi)的方法都需要進(jìn)行method swizzling怎棱。
- iOS 8上,NSURLSessionTask是唯一實(shí)現(xiàn)了resume和suspend方法的類(lèi)绷跑。這也意味著其是唯一需要進(jìn)行method swizzling的類(lèi)
- 因?yàn)镹SURLSessionTask并不是在每個(gè)iOS版本中都存在拳恋,所以把這些放在此處(即load函數(shù)中),比如給一個(gè)dummy class添加swizzled方法都會(huì)變得很方便砸捏,管理起來(lái)也方便谬运。
6. AFNetworking 2.x
6.1.核心類(lèi)
1)AFURLConnectionOperation
AF2.x是基于NSURLConnection來(lái)封裝的,而NSURLConnection的創(chuàng)建以及數(shù)據(jù)請(qǐng)求垦藏,就被封裝在AFURLConnectionOperation這個(gè)類(lèi)中
2)AFHTTPRequestOperation
繼承自AFURLConnectionOperation
3)AFHTTPRequestOperationManager
管理這些這些operation
6.2.init
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//添加端口梆暖,防止runloop直接退出
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
這是一個(gè)單例,用NSThread創(chuàng)建了一個(gè)線(xiàn)程膝藕,并且為這個(gè)線(xiàn)程添加了一個(gè)runloop式廷,并且加了一個(gè)NSMachPort,來(lái)防止runloop直接退出芭挽。
這條線(xiàn)程就是AF用來(lái)發(fā)起網(wǎng)絡(luò)請(qǐng)求滑废,并且接受網(wǎng)絡(luò)請(qǐng)求回調(diào)的線(xiàn)程,僅僅就這一條線(xiàn)程
6.3.- (void)setState:(AFOperationState)state
- (void)setState:(AFOperationState)state {
//判斷從當(dāng)前狀態(tài)到另一個(gè)狀態(tài)是不是合理袜爪,在加上現(xiàn)在是否取消蠕趁。。大神的框架就是屌啊辛馆,這判斷嚴(yán)謹(jǐn)?shù)陌陈!R粚訉? if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
return;
}
[self.lock lock];
//拿到對(duì)應(yīng)的父類(lèi)管理當(dāng)前線(xiàn)程周期的key
NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
NSString *newStateKey = AFKeyPathFromOperationState(state);
//發(fā)出KVO
[self willChangeValueForKey:newStateKey];
[self willChangeValueForKey:oldStateKey];
_state = state;
[self didChangeValueForKey:oldStateKey];
[self didChangeValueForKey:newStateKey];
[self.lock unlock];
}
這個(gè)方法改變state的時(shí)候昙篙,并且發(fā)送了KVO腊状。大家了解NSOperationQueue就知道,如果對(duì)應(yīng)的operation的屬性finnished被設(shè)置為YES苔可,則代表當(dāng)前operation結(jié)束了缴挖,會(huì)把operation從隊(duì)列中移除,并且調(diào)用operation的completionBlock焚辅。這點(diǎn)很重要映屋,因?yàn)槲覀冋?qǐng)求到的數(shù)據(jù)就是從這個(gè)completionBlock中傳遞回去的(下面接著講這個(gè)完成Block,就能從這里對(duì)接上了)同蜻。
6.4.為什么AF2.x需要一條常駐線(xiàn)程棚点?
首先如果我們用NSURLConnection,我們?yōu)榱双@取請(qǐng)求結(jié)果有以下三種選擇:
1.在主線(xiàn)程調(diào)異步接口
2.每一個(gè)請(qǐng)求用一個(gè)線(xiàn)程湾蔓,對(duì)應(yīng)一個(gè)runloop瘫析,然后等待結(jié)果回調(diào)。
3.只用一條線(xiàn)程,一個(gè)runloop颁股,所有結(jié)果回調(diào)在這個(gè)線(xiàn)程上么库。
很顯然AF選擇的是第3種方式,創(chuàng)建了一條常駐線(xiàn)程專(zhuān)門(mén)處理所有請(qǐng)求的回調(diào)事件.這個(gè)模型跟nodejs有點(diǎn)類(lèi)似
7.總結(jié)
一. 首先我們需要明確一點(diǎn)的是:
相對(duì)于AFNetworking2.x甘有,AFNetworking3.x確實(shí)沒(méi)那么有用了诉儒。AFNetworking之前的核心作用就是為了幫我們?nèi)フ{(diào)度所有的請(qǐng)求。但是最核心地方卻被蘋(píng)果的NSURLSession給借鑒過(guò)去
二.除此之外
1.首先它幫我們做了各種請(qǐng)求方式request的拼接亏掀。
2.它還幫我們做了一些公用參數(shù)(session級(jí)別的)忱反,和一些私用參數(shù)(task級(jí)別的)的分離。
3.它幫我們做了自定義的https認(rèn)證處理滤愕。
4.對(duì)于請(qǐng)求到的數(shù)據(jù)温算,AF幫我們做了各種格式的數(shù)據(jù)解析,并且支持我們?cè)O(shè)置自定義的code范圍间影,自定義的數(shù)據(jù)方式注竿。
5.對(duì)于成功和失敗的回調(diào)處理。
6.繞開(kāi)了很多的坑:回調(diào)線(xiàn)程數(shù)設(shè)置為1的問(wèn)題魂贬,系統(tǒng)內(nèi)部并行創(chuàng)建task導(dǎo)致id不唯一等等
8.UIImageView+AFNetworking
8.1. AFImageDownloader
AF自己控制的圖片緩存用AFAutoPurgingImageCache巩割,而NSUrlRequest的緩存由它自己內(nèi)部根據(jù)策略去控制,用的是NSURLCache付燥,不歸AF處理宣谈,只需在configuration中設(shè)置上即可
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache
//創(chuàng)建一個(gè)串行的queue
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
//創(chuàng)建并行queue
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
這個(gè)串行queue,是用來(lái)做內(nèi)部生成task等等一系列業(yè)務(wù)邏輯的。它保證了我們?cè)谶@些邏輯處理中的線(xiàn)程安全問(wèn)題(迷惑的接著往下看)键科。
這個(gè)并行queue闻丑,被用來(lái)做網(wǎng)絡(luò)請(qǐng)求完成的數(shù)據(jù)回調(diào)。
8.2. AFAutoPurgingImageCache
- (instancetype)init {
//默認(rèn)為內(nèi)存100M勋颖,后者為緩存溢出后保留的內(nèi)存
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
聲明了一個(gè)默認(rèn)的內(nèi)存緩存大小100M嗦嗡,還有一個(gè)意思是如果超出100M之后,我們?nèi)デ宄彺娣沽幔藭r(shí)仍要保留的緩存大小60M
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity
//并行的queue
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
創(chuàng)建了一個(gè)并行queue酸钦,這個(gè)并行queue,這個(gè)類(lèi)除了初始化以外咱枉,所有的方法都是在這個(gè)并行queue中調(diào)用的。
//移除所有圖片
- (BOOL)removeAllImages {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
if (self.cachedImages.count > 0) {
[self.cachedImages removeAllObjects];
self.currentMemoryUsage = 0;
removed = YES;
}
});
return removed;
}
1)這里我們可以看到使用了dispatch_barrier_sync徒恋,這里沒(méi)有用鎖蚕断,但是因?yàn)槭褂昧薲ispatch_barrier_sync,不僅同步了synchronizationQueue隊(duì)列入挣,而且阻塞了當(dāng)前線(xiàn)程亿乳,所以保證了里面執(zhí)行代碼的線(xiàn)程安全問(wèn)題。
2)在這里其實(shí)使用鎖也可以,但是AF在這的處理卻是使用同步的機(jī)制來(lái)保證線(xiàn)程安全葛假,或許這跟圖片的加載緩存的使用場(chǎng)景障陶,高頻次有關(guān)系,在這里使用sync聊训,并不需要在去開(kāi)辟新的線(xiàn)程抱究,浪費(fèi)性能,只需要在原有線(xiàn)程带斑,提交到synchronizationQueue隊(duì)列中鼓寺,阻塞的執(zhí)行即可。這樣省去大量的開(kāi)辟線(xiàn)程與使用鎖帶來(lái)的性能消耗勋磕。
//添加image到cache里
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
//用dispatch_barrier_async妈候,來(lái)同步這個(gè)并行隊(duì)列
dispatch_barrier_async(self.synchronizationQueue, ^{
//生成cache對(duì)象
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
//去之前cache的字典里取
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
//如果有被緩存過(guò)
if (previousCachedImage != nil) {
//當(dāng)前已經(jīng)使用的內(nèi)存大小減去圖片的大小
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
//把新cache的image加上去
self.cachedImages[identifier] = cacheImage;
//加上內(nèi)存大小
self.currentMemoryUsage += cacheImage.totalBytes;
});
//做緩存溢出的清除,清除的是早期的緩存
dispatch_barrier_async(self.synchronizationQueue, ^{
//如果使用的內(nèi)存大于我們?cè)O(shè)置的內(nèi)存容量
if (self.currentMemoryUsage > self.memoryCapacity) {
//拿到使用內(nèi)存 - 被清空后首選內(nèi)存 = 需要被清除的內(nèi)存
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
//拿到所有緩存的數(shù)據(jù)
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
//根據(jù)lastAccessDate排序 升序挂滓,越晚的越后面
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
ascending:YES];
[sortedImages sortUsingDescriptors:@[sortDescriptor]];
UInt64 bytesPurged = 0;
//移除早期的cache bytesToPurge大小
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break ;
}
}
//減去被清掉的內(nèi)存
self.currentMemoryUsage -= bytesPurged;
}
});
}
當(dāng)然在這里更需要說(shuō)一說(shuō)的是dispatch_barrier_async苦银,這里整個(gè)類(lèi)都沒(méi)有使用dispatch_async,所以不存在是為了做一個(gè)柵欄赶站,來(lái)同步上下文的線(xiàn)程幔虏。其實(shí)它在本類(lèi)中的作用很簡(jiǎn)單,就是一個(gè)串行執(zhí)行亲怠。
講到這所计,小伙伴們又疑惑了,既然就是只是為了串行团秽,那為什么我們不用一個(gè)串行queue就得了主胧?非得用dispatch_barrier_async干嘛?其實(shí)小伙伴要是看的仔細(xì)习勤,就明白了踪栋,上文我們說(shuō)過(guò),我們要用dispatch_barrier_sync來(lái)保證線(xiàn)程安全图毕。如果我們使用串行queue,那么線(xiàn)程是極其容易死鎖的夷都。
8.3.流程
接下來(lái)我們來(lái)總結(jié)一下整個(gè)請(qǐng)求圖片,緩存予颤,然后設(shè)置圖片的流程:
調(diào)用- (void)setImageWithURL:(NSURL *)url;時(shí)囤官,我們生成
AFImageDownloader單例,并替我們請(qǐng)求數(shù)據(jù)蛤虐。
而AFImageDownloader會(huì)生成一個(gè)AFAutoPurgingImageCache替我們緩存生成的數(shù)據(jù)党饮。當(dāng)然我們?cè)O(shè)置的時(shí)候,給session的configuration設(shè)置了一個(gè)系統(tǒng)級(jí)別的緩存NSUrlCache,這兩者是互相獨(dú)立工作的驳庭,互不影響的刑顺。
然后AFImageDownloader氯窍,就實(shí)現(xiàn)下載和協(xié)調(diào)AFAutoPurgingImageCache去緩存,還有一些取消下載的方法蹲堂。然后通過(guò)回調(diào)把數(shù)據(jù)給到我們的類(lèi)目UIImageView+AFNetworking,如果成功獲取數(shù)據(jù)狼讨,則由類(lèi)目設(shè)置上圖片,整個(gè)流程結(jié)束柒竞。