AFNetworking筆記

1.整體架構(gòu)

2702646-10294db19b1aedfd.png.jpeg

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)用了


2702646-5a404cc7d92fb8ec.png.jpeg

而只轉(zhuǎn)發(fā)了其中3條到AF自定義的delegate中:

2702646-e6469f92ca6a550e.png.jpeg

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é)束柒竞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末政供,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子能犯,更是在濱河造成了極大的恐慌鲫骗,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踩晶,死亡現(xiàn)場(chǎng)離奇詭異执泰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)渡蜻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)术吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人茸苇,你說(shuō)我怎么就攤上這事排苍。” “怎么了学密?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵淘衙,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我腻暮,道長(zhǎng)彤守,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任哭靖,我火速辦了婚禮具垫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘试幽。我一直安慰自己筝蚕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布铺坞。 她就那樣靜靜地躺著起宽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪济榨。 梳的紋絲不亂的頭發(fā)上燎含,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音腿短,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛橘忱,可吹牛的內(nèi)容都是我干的赴魁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钝诚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颖御!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起凝颇,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤潘拱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后拧略,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體芦岂,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年垫蛆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了禽最。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡袱饭,死狀恐怖川无,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情虑乖,我是刑警寧澤懦趋,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站疹味,受9級(jí)特大地震影響仅叫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜佛猛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一惑芭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧继找,春花似錦遂跟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至边臼,卻和暖如春哄尔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柠并。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工岭接, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留富拗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓鸣戴,卻偏偏與公主長(zhǎng)得像啃沪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窄锅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 接著上一篇的內(nèi)容往下講入偷,如果沒(méi)看過(guò)上一篇內(nèi)容可以點(diǎn)這: AFNetworking到底做了什么? 之前我們講到NSU...
    涂耀輝閱讀 21,176評(píng)論 28 160
  • 最近在看runloop時(shí)看到不少blog都說(shuō)AFNetworking追驴,有使用到runloop創(chuàng)建一個(gè)子線(xiàn)程并保持線(xiàn)...
    學(xué)習(xí)無(wú)底閱讀 1,391評(píng)論 2 12
  • 寫(xiě)在開(kāi)頭: 大概回憶下,之前我們講了AFNetworking整個(gè)網(wǎng)絡(luò)請(qǐng)求的流程疏之,包括request的拼接殿雪,sess...
    涂耀輝閱讀 12,278評(píng)論 30 89
  • 寫(xiě)在開(kāi)頭: 作為一個(gè)iOS開(kāi)發(fā)冠摄,也許你不知道NSUrlRequest、不知道NSUrlConnection几缭、也不知...
    涂耀輝閱讀 98,711評(píng)論 172 1,346
  • 從哪說(shuō)起呢河泳? 單純講多線(xiàn)程編程真的不知道從哪下嘴。年栓。 不如我直接引用一個(gè)最簡(jiǎn)單的問(wèn)題拆挥,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,762評(píng)論 1 17