AFNetworking 之 UIKit 擴充套件與快取實現

發表於2016-12-18
112702646-426b7ad3b6d4e3ba
寫在開頭:
  • 大概回憶下,之前我們講了AFNetworking整個網路請求的流程,包括request的拼接,session代理的轉發,response的解析。以及對一些bug的適配,如果你還沒有看過,可以點這裡:
    AFNetworking到底做了什麼?
    AFNetworking到底做了什麼(二)?
  • 除此之外我們還單獨的開了一篇講了AF對https的處理:
    AFNetworking之於https認證
  • 本文將涉及部分AF對UIKit的擴充套件與圖片下載相關快取的實現,文章內容相對獨立,如果沒看過前文,也不影響閱讀。
回到正文:

我們來看看AF對UIkit的擴充套件:

122702646-91661ed549cf34d2
UIKit擴充套件.png
一共如上這個多類,下面我們開始著重講其中兩個UIKit的擴充套件:
  • 一個是我們網路請求時狀態列的小菊花。
  • 一個是我們幾乎都用到過請求網路圖片的如下一行方法:
我們開始吧:
1.AFNetworkActivityIndicatorManager

這個類的作用相當簡單,就是當網路請求的時候,狀態列上的小菊花就會開始轉:

132702646-0caaf80ee0a9725d
小菊花.png

需要的程式碼也很簡單,只需在你需要它的位置中(比如AppDelegate)匯入類,並加一行程式碼即可:

接下來我們來講講這個類的實現:
  • 這個類的實現也非常簡單,還記得我們之前講的AF對NSURLSessionTask中做了一個Method Swizzling嗎?大意是把它的resumesuspend方法做了一個替換,在原有實現的基礎上新增了一個通知的傳送。
  • 這個類就是基於這兩個通知和task完成的通知來實現的。
首先我們來看看它的初始化方法:

  • 初始化如上,設定了一個state,這個state是一個列舉:

    這個state一共如上4種狀態,其中兩種應該很好理解,而延遲開始和延遲結束怎麼理解呢?
    • 原來這是AF對請求菊花顯示做的一個優化處理,試問如果一個請求時間很短,那麼菊花很可能閃一下就結束了。如果很多請求過來,那麼菊花會不停的閃啊閃,這顯然並不是我們想要的效果。
    • 所以多了這兩個引數:
      1)在一個請求開始的時候,我延遲一會在去轉菊花,如果在這延遲時間內,請求結束了,那麼我就不需要去轉菊花了。
      2)但是一旦轉菊花開始,哪怕很短請求就結束了,我們還是會去轉一個時間再去結束,這時間就是延遲結束的時間。
  • 緊接著我們監聽了三個通知,用來監聽當前正在進行的網路請求的狀態。
  • 然後設定了我們前面提到的這個轉菊花延遲開始和延遲結束的時間,這兩個預設值如下:

接著我們來看看三個通知觸發呼叫的方法:

方法很簡單,就是開始的時候增加了請求活躍數,結束則減少。呼叫瞭如下兩個方法進行加減:

方法做了什麼應該很容易看明白,這裡需要注意的是,task的幾個狀態的通知,是會在多執行緒的環境下傳送過來的。所以這裡對活躍數的加減,都用了@synchronized這種方式的鎖,進行了執行緒保護。然後回到主執行緒呼叫了updateCurrentStateForNetworkActivityChange

我們接著來看看這個方法:

  • 這個方法先是判斷了我們一開始設定是否需要菊花的self.enabled,如果需要,才執行。
  • 這裡主要是根據當前的狀態,來判斷下一個狀態應該是什麼。其中有這麼一個屬性self.isNetworkActivityOccurring:

    那麼這個方法應該不難理解了。

這個類複寫了currentState的set方法,每當我們改變這個state,就會觸發set方法,而怎麼該轉菊花也在該方法中:

這個set方法就是這個類最核心的方法了。它的作用如下:

  • 這裡根據當前狀態,是否需要開始執行一個延遲開始或者延遲完成,又或者是否需要取消這兩個延遲。
  • 還判斷了,是否需要去轉狀態列的菊花,呼叫了setNetworkActivityIndicatorVisible:方法:
    • 這個方法就是用來控制菊花是否轉。並且支援一個自定義的Block,我們可以自己去拿到這個菊花是否應該轉的狀態值,去做一些自定義的處理。
    • 如果我們沒有實現這個Block,則呼叫:

      去轉菊花。

回到state的set方法中,我們除了控制菊花去轉,還呼叫了以下4個方法:

這4個方法分別是開始延遲執行一個方法,和結束的時候延遲執行一個方法,和對應這兩個方法的取消。其作用,註釋應該很容易理解。
我們繼續往下看,這兩個延遲呼叫的到底是什麼:

一個開始,一個完成呼叫,都設定了不同的currentState的值,又回到之前stateset方法中了。

至此這個AFNetworkActivityIndicatorManager類就講完了,程式碼還是相當簡單明瞭的。

142702646-426da66cf4d16567
2.UIImageView+AFNetworking

接下來我們來講一個我們經常用的方法,這個方法的實現類是:UIImageView+AFNetworking.h
這是個類目,並且給UIImageView擴充套件了4個方法:

  • 前兩個想必不用我說了,沒有誰沒用過吧…就是給一個UIImageView去非同步的請求一張圖片,並且可以設定一張佔點陣圖。
  • 第3個方法設定一張圖,並且可以拿到成功和失敗的回撥。
  • 第4個方法,可以取消當前的圖片設定請求。

無論SDWebImage,還是YYKit,或者AF,都實現了這麼個類目。
AF關於這個類目UIImageView+AFNetworking的實現,依賴於這麼兩個類:AFImageDownloaderAFAutoPurgingImageCache
當然AFImageDownloader中,關於圖片資料請求的部分,還是使用AFURLSessionManager來實現的。

接下來我們就來看看AFImageDownloader:

先看看初始化方法:

該類為單例,上述方法中,建立了一個sessionManager,這個sessionManager將用於我們之後的網路請求。從這裡我們可以看到,這個類的網路請求都是基於之前AF自己封裝的AFHTTPSessionManager

  • 在這裡初始化了一系列的物件,需要講一下的是AFImageDownloadPrioritizationFIFO,這個一個列舉值:

    這個列舉值代表著,一堆圖片下載,執行任務的順序。
  • 還有一個AFAutoPurgingImageCache的建立,這個類是AF做圖片快取用的。這裡我們暫時就這麼理解它,講完當前類,我們再來補充它。
  • 除此之外,我們還看到一個cache:


    大家看到這可能迷惑了,怎麼這麼多cache,那AF做圖片快取到底用哪個呢?答案是AF自己控制的圖片快取用AFAutoPurgingImageCache,而NSUrlRequest的快取由它自己內部根據策略去控制,用的是NSURLCache,不歸AF處理,只需在configuration中設定上即可。
    • 那麼看到這有些小夥伴又要問了,為什麼不直接用NSURLCache,還要自定義一個AFAutoPurgingImageCache呢?原來是因為NSURLCache的諸多限制,例如只支援get請求等等。而且因為是系統維護的,我們自己的可控度不強,並且如果需要做一些自定義的快取處理,無法實現。
    • 更多關於NSURLCache的內容,大家可以自行查閱。

接著上面的方法呼叫到這個最終的初始化方法中:

這邊初始化了一些屬性,這些屬性跟著註釋看應該很容易明白其作用。主要需要注意的就是,這裡建立了兩個queue:一個序列的請求queue,和一個並行的響應queue。

  • 這個序列queue,是用來做內部生成task等等一系列業務邏輯的。它保證了我們在這些邏輯處理中的執行緒安全問題(迷惑的接著往下看)。
  • 這個並行queue,被用來做網路請求完成的資料回撥。

接下來我們來看看它的建立請求task的方法:

就這麼一個非常非常長的方法,這個方法執行的內容都是在我們之前建立的序列queue中,同步的執行的,這是因為這個方法絕大多數的操作都是需要執行緒安全的。可以對著原始碼和註釋來看,我們在這講下它做了什麼:

  1. 首先做了一個url的判斷,如果為空則返回失敗Block。
  2. 判斷這個需要請求的url,是不是已經被生成的task中,如果是的話,則多新增一個回撥處理就可以。回撥處理物件為AFImageDownloaderResponseHandler。這個類非常簡單,總共就如下3個屬性:

    當這個task完成的時候,會呼叫我們新增的回撥。
  3. 關於AFImageDownloaderMergedTask,我們在這裡都用的是這種型別的task,其實這個task也很簡單:

    其實就是除了NSURLSessionDataTask,多加了幾個引數,URLIdentifieridentifier都是用來標識這個task的,responseHandlers是用來儲存task完成後的回撥的,裡面可以存一組,當任務完成時候,裡面的回撥都會被呼叫。
  4. 接著去根據快取策略,去載入快取,如果有快取,從self.imageCache中返回快取,否則繼續往下走。
  5. 走到這說明沒相同url的task,也沒有cache,那麼就開始一個新的task,呼叫的是AFUrlSessionManager裡的請求方法生成了一個task(這裡我們就不贅述了,可以看之前的樓主之前的文章)。然後做了請求完成的處理。注意,這裡處理實在我們一開始初始化的並行queue:self.responseQueue中的,這裡的響應處理是多執行緒併發進行的。
    1)完成,則呼叫如下方法把這個task從全域性字典中移除:

    2)去迴圈這個task的responseHandlers,呼叫它的成功或者失敗的回撥。
    3)並且呼叫下面兩個方法,去減少正在請求的任務數,和開啟下一個任務:

    這裡需要注意的是,跟我們本類的一些資料相關的操作,都是在我們一開始的序列queue中同步進行的。
    4)除此之外,如果成功,還把成功請求到的資料,加到AF自定義的cache中:
  6. NSUUID生成的唯一標識,去生成AFImageDownloaderResponseHandler,然後生成一個AFImageDownloaderMergedTask,把之前第5步生成的createdTask和回撥都繫結給這個AF自定義可合併回撥的task,然後這個task加到全域性的task對映字典中,key為url:
  7. 判斷當前正在下載的任務是否超過最大並行數,如果沒有則開始下載,否則先加到等待的陣列中去:


    • 先判斷並行數限制,如果小於最大限制,則開始下載,把當前活躍的request數量+1。
    • 如果暫時不能下載,被加到等待下載的陣列中去的話,會根據我們一開始設定的下載策略,是先進先出,還是後進先出,去插入這個下載任務。
  8. 最後判斷這個mergeTask是否為空。不為空,我們生成了一個AFImageDownloadReceipt,繫結了一個UUID。否則為空返回nil:

    這個AFImageDownloadReceipt僅僅是多封裝了一個UUID:

    這麼封裝是為了標識每一個task,我們後面可以根據這個AFImageDownloadReceipt來對task做取消操作。

這個AFImageDownloader中最核心的方法基本就講完了,還剩下一些方法沒講,像前面講到的task的取消的方法:

方法比較簡單,大家自己看看就好。至此`AFImageDownloader這個類講完了。如果大家看的感覺比較繞,沒關係,等到最後我們一起來總結一下,捋一捋。

142702646-426da66cf4d16567

我們之前講到AFAutoPurgingImageCache這個類略過去了,現在我們就來補充一下這個類的相關內容:
首先來講講這個類的作用,它是AF自定義用來做圖片快取的。我們來看看它的初始化方法:

初始化方法很簡單,總結一下:

  1. 宣告瞭一個預設的記憶體快取大小100M,還有一個意思是如果超出100M之後,我們去清除快取,此時仍要保留的快取大小60M。(如果還是不理解,可以看後文,原始碼中會講到)
  2. 建立了一個並行queue,這個並行queue,這個類除了初始化以外,所有的方法都是在這個並行queue中呼叫的。
  3. 建立了一個cache字典,我們所有的快取資料,都被儲存在這個字典中,key為url,value為AFCachedImage
    關於這個AFCachedImage,其實就是Image之外封裝了幾個關於這個快取的引數,如下:
  4. 新增了一個通知,監聽記憶體警告,當發成記憶體警告,呼叫該方法,移除所有的快取,並且把當前快取數置為0:

    注意這個類大量的使用了dispatch_barrier_syncdispatch_barrier_async,小夥伴們如果對這兩個方法有任何疑惑,可以看看這篇文章:dispatch_barrier_async與dispatch_barrier_sync異同
    1)這裡我們可以看到使用了dispatch_barrier_sync,這裡沒有用鎖,但是因為使用了dispatch_barrier_sync,不僅同步了synchronizationQueue佇列,而且阻塞了當前執行緒,所以保證了裡面執行程式碼的執行緒安全問題。
    2)在這裡其實使用鎖也可以,但是AF在這的處理卻是使用同步的機制來保證執行緒安全,或許這跟圖片的載入快取的使用場景,高頻次有關係,在這裡使用sync,並不需要在去開闢新的執行緒,浪費效能,只需要在原有執行緒,提交到synchronizationQueue佇列中,阻塞的執行即可。這樣省去大量的開闢執行緒與使用鎖帶來的效能消耗。(當然這僅僅是我的一個猜測,有不同意見的朋友歡迎討論~)
    • 在這裡用了dispatch_barrier_sync,因為synchronizationQueue是個並行queue,所以在這裡不會出現死鎖的問題。
    • 關於保證執行緒安全的同時,同步還是非同步,與效能方面的考量,可以參考這篇文章:Objc的底層併發API

接著我們來看看這個類最核心的一個方法:

看註釋應該很容易明白,這個方法做了兩件事:

  1. 設定快取到字典裡,並且把對應的快取大小設定到當前已快取的數量屬性中。
  2. 判斷是快取超出了我們設定的最大快取100M,如果是的話,則清除掉部分早時間的快取,清除到快取小於我們溢位後保留的記憶體60M以內。

當然在這裡更需要說一說的是dispatch_barrier_async,這裡整個類都沒有使用dispatch_async,所以不存在是為了做一個柵欄,來同步上下文的執行緒。其實它在本類中的作用很簡單,就是一個序列執行。

  • 講到這,小夥伴們又疑惑了,既然就是隻是為了序列,那為什麼我們不用一個序列queue就得了?非得用dispatch_barrier_async幹嘛?其實小夥伴要是看的仔細,就明白了,上文我們說過,我們要用dispatch_barrier_sync來保證執行緒安全。如果我們使用序列queue,那麼執行緒是極其容易死鎖的。

還有剩下的幾個方法:

這幾個方法都很簡單,大家自己看看就好了,就不贅述了。至此AFAutoPurgingImageCache也講完了,我們還是等到最後再來總結。

142702646-426da66cf4d16567

我們繞了一大圈,總算回到了UIImageView+AFNetworking這個類,現在圖片下載的方法,和快取的方法都有了,實現這個類也是水到渠成的事了。

我們來看下面我們絕大多數人很熟悉的方法,看看它的實現:

上述方法按順序往下呼叫,第二個方法給head的Accept型別設定為Image。接著呼叫到第三個方法,也是這個類目唯一一個重要的方法:

這個方法,細節的地方可以關注註釋,這裡總結一下做了什麼:
1)去判斷url是否為空,如果為空則取消task,呼叫如下方法:

  • 這裡注意cancelImageDownloadTask中,呼叫了self.af_activeImageDownloadReceipt這麼一個屬性,看看定義的地方:

    我們現在是給UIImageView新增的一個類目,所以我們無法直接新增屬性,而是使用的是runtime的方式來生成set和get方法生成了一個AFImageDownloadReceipt型別的屬性。看過上文應該知道這個物件裡面就一個task和一個UUID。這個屬性就是我們這次下載任務相關聯的資訊。

2)然後做了一系列判斷,見註釋。
3)然後生成了一個我們之前分析過得AFImageDownloader,然後去獲取快取,如果有快取,則直接讀快取。還記得AFImageDownloader裡也有一個讀快取的方法麼?那個是和cachePolicy相關的,而這個是有快取的話直接讀取。不明白的可以回過頭去看看。
4)走到這說明沒快取了,然後就去用AFImageDownloader,我們之前講過的方法,去請求圖片。完成後,則呼叫成功或者失敗的回撥,並且置空屬性self.af_activeImageDownloadReceipt,成功則設定圖片。

除此之外還有一個取消這次任務的方法:

其實也是去呼叫我們之前講過的AFImageDownloader的取消方法。

這個類總共就這麼幾行程式碼,就完成了我們幾乎沒有人不用的,設定ImageView圖片的方法。當然真正的難點在於AFImageDownloaderAFAutoPurgingImageCache

接下來我們來總結一下整個請求圖片,快取,然後設定圖片的流程:
  • 呼叫- (void)setImageWithURL:(NSURL *)url;時,我們生成
    AFImageDownloader單例,並替我們請求資料。
  • AFImageDownloader會生成一個AFAutoPurgingImageCache替我們快取生成的資料。當然我們設定的時候,給sessionconfiguration設定了一個系統級別的快取NSUrlCache,這兩者是互相獨立工作的,互不影響的。
  • 然後AFImageDownloader,就實現下載和協調AFAutoPurgingImageCache去快取,還有一些取消下載的方法。然後通過回撥把資料給到我們的類目UIImageView+AFNetworking,如果成功獲取資料,則由類目設定上圖片,整個流程結束。

經過這三個檔案:
UIImageView+AFNetworkingAFImageDownloaderAFAutoPurgingImageCache,至此整個設定網路圖片的方法結束了。

寫在最後:
  • 對於UIKit的總結,我們就到此為止了,其它部分的擴充套件,小夥伴們可以自行閱讀,都很簡單,基本上每個類200行左右的程式碼。核心功能基本上都是圍繞AFURLSessionManager實現的。
  • 本來想本篇放在三裡面完結,想想還是覺得自己…too young too simple…
    但是下一篇應該是一個結束了,我們會講講AF2.x,然後詳細總結一下AF存在的意義。大家任何有疑問或者不同意見的,歡迎評論,樓主會一一回復的。求關注,求贊?。感謝~~
後續文章:

AFNetworking到底做了什麼?(終)

相關文章