SDWebImage Source Probe: Downloader

發表於2016-10-07
 為了進行圖片下載操作,通過 SDWebImageManager 這座橋樑,有效控制了圖片下載的時機和同快取的協同操作。這篇來關注一下在 SD 中,Downloader Class 的具體實現。

Downloader 中的一些列舉

SDWebImageDownloader.m 中,可以發現這麼一個屬性:

NSOperation 表示一個獨立的控制單元,也就是我們所說的執行緒。而 NSOperationQueue 控制著這些並行操作的執行,以佇列的資料結構特點,從而實現執行緒優先順序的控制。而在 SDWebImage 中,很顯然是用來管理 SDWebImageDownloaderOperation 。對於 SDWebImageDownloaderOperation 後面將會單獨放在一篇博文中介紹。

同 Manager 一樣,我們先來看看在 .h 檔案中所有的下載模式列舉。

另外,對於下載順序,SD 也為我們提供了兩種不同的下載順序列舉:

options 列舉已經幾乎將所有的開發場景所需要的模式考慮進來。下面我們來看一看具體的實現程式碼。

Downloader 的私有成員物件

先來看下 Class 的 property 物件的作用:

由於需要保證多個圖片可以同時下載,為了保證 URLCallbacks 的執行緒安全,我們使用 GCD 中的 dispatch_barrier_sync 為程式設定柵欄(barrier),它會等待所有位於柵欄函式之前的操作執行完成後再執行,並且在柵欄函式執行完成後,其後續操作才會開始執行,這個函式需要同 dispatch_queue_create 生成的 Dispatch 的同步佇列(Concurrent Dispatch Queue)共同使用。

有了這些對於類成員的認識,開始閱讀 Downloader 的原始碼:

整個流程已經瞭解,下面分析一些細小的細節問題:

全域性字典,將 URL 與回撥 block 的對映容器

在執行進行中 block、完成 block 的時候,都會使用以上這幾行程式碼。其作用是為了維護一個字典,key 為圖片的唯一標識 url ,值為一個 block 的陣列,來統一管理這些回撥方法。其大致的結構圖如下表示:

11urlcallbacks
(圖片來源:polobymulberry)

執行過程中的 block 的時候,在初始化字典管理的時候使用了 dispatch_sync 同步執行操作,而沒有增加柵欄函式(註釋中為增加柵欄函式)。但在對於完成 block 的管理時,為了保證執行緒安全的競爭選擇問題,SD 作者選用了柵欄函式對執行緒進行了先後執行的規定。為什麼這裡不用柵欄呢?筆者的理解如下:由於這兩個位置,都是對於 URLCallbacks 的讀寫操作,而在這之前是沒有任何更新 URLCallbacks 的操作,所以不需要設定柵欄,只需要同步繼續即可。而對於柵欄函式,是用在非同步操作中對於操作順序進行控制,由於 SD 需要支援多圖片同時下載,所以需要在每次的 URLCallbacks 寫資料結束後,再進行讀操作。

NSMutableURLRequest 網路請求

initWithURL 的作用是根據 url 、快取策略(Cache Policy)、下載最大時限(Time Out Interval)來產生一個 NSURLRequest。先來看下快取策略的選擇:

  • SDWebImageDownloaderUseNSURLCache:在 SDWebImage 中,預設條件下,請求是不使用 NSURLCache 的。如果使用該選項,NSURLCache 就應該使用預設的快取策略 NSURLRequestUseProtocolCachePolicy
  • NSURLRequestUseProtocolCachePolicy:對特定 url 請求使用網路協議(例如 HTTP)中實現的快取邏輯。這是一個預設的策略。該策略表示如果快取不存在,直接從服務端獲取。如果快取存在,會根據 Response 中的 Cache-Control 欄位判斷下一步操作。例如:當 Cache-Control 欄位為 must-revalidata ,則會詢問服務端該資料是否有更新,無更新則返回給使用者快取資料,若已經更新,則請求伺服器以獲取最新資料。
  • NSURLRequestReloadIgnoringLocalCacheData:資料需要從原始地址(一般就是重新從伺服器獲取)。不使用現有快取。

後面就是對於 request 的一些屬性的設定,從屬性名上可以看出使用的是 HTTP 協議:

  • 要點一:HTTPShouldHandleCookies 如果設定為 YES,在處理時我們直接查詢 NSHTTPCookieStore 中的 cookies 即可。HTTPShouldHandleCookies 這個策略表示是否應該給 Request 設定 cookie 並伴隨著 Request 一起傳送出去。然後 Response 返回的 cookie 會繼續根據訪問策略(Cookie Acceptance Policy)接收到系統中。
  • 要點二:HTTPShouldUsePipelining 表示 receiver (常常理解為 client 客戶端)的下一個資訊是否必須等到上一個請求回覆才能傳送。如果為 YES 表示可以, NO 反之。這個就是我們常常提到的 HTTP 管線化(HTTP Pipelining),如此可以顯著降低請求的載入時間。
  • 要點三:headersFilter 是使用自定義方法來設定 HTTP 的 Head Filed。這裡可以看下 HTTPHeader 的初始化(下載 webp 圖片與通常情況下的 header 不同):

NSURLCredential 身份認證

web 服務可以在返回 HTTP 響應時附帶認證要求的 Challenge,作用是詢問 HTTP 請求的發起方是誰,這時候發起方應提供正確的使用者名稱和密碼(認證資訊),然後 web 服務才會返回真正的 HTTP 響應。

收到認證要求時,NSURLConnection 的委託物件會收到相應的訊息並得到一個 NSURLAuthenticationChallenge 例項。該例項的傳送方遵守 NSURLAuthenticationChallengeSender 協議。為了繼續收到真實的資料,需要向該傳送方向發回一個 NSURLCredential 例項。

當已經有用 NSURLCredential ,則直接使用,沒有的話則重新構建一個例項並儲存下來。NSURLCredential 在其中的作用就是快取對於證書的授權處理。這是對於 https 協議而言,如果想了解更多建議閱讀 Foundation的官方文件

總結

在 Downloader 中,主要的操作就是用於組織一個 URLCallbacks 字典,用於管理圖片指定的進行 block 、完成 block。並且,在 downloadImageWithURL: 方法中,Downloader 其實一直在更新一個 operation 並作為返回值。所以,Downloader 的主要作用是實現多圖片非同步下載請求,並將其封裝為一個 operation 提交給上層統一管理。

下一篇主要講解一下 DownloaderOperation 下載操作任務管理。

相關文章