SDWebImage 源碼筆記

目錄

  • 簡介

    • 設計目的

    • 特性

    • SDWebImage 與其他框架的對比

    • 常見問題

    • 用法

    • SDWebImage 4.0 遷移指南

  • 實現原理

    • 架構圖

    • 流程圖

    • 目錄結構

    • 核心邏輯

  • 實現細節

    • 1. 圖片下載

      • 1.1 SDWebImageDownloader

      • 1.2 SDWebImageDownloader

    • 2. 圖片緩存——SDImageCache

    • 3. 圖片加載管理器——SDWebImageManager

    • 4. 設置 UIImageView 的圖片——UIImageView+WebCache

  • 知識點

  • 收穫與疑問

  • 啓發與實踐

  • 延伸閱讀

一、簡介

1. 設計目的

SDWebImage 提供了 UIImageView、UIButton 、MKAnnotationView 的圖片下載分類,只要一行代碼就可以實現圖片異步下載和緩存功能。這樣開發者就無須花太多精力在圖片下載細節上,專心處理業務邏輯。

2. 特性

  • 提供 UIImageView, UIButton, MKAnnotationView 的分類,用來顯示網絡圖片,以及緩存管理

  • 異步下載圖片

  • 異步緩存(內存+磁盤),並且自動管理緩存有效性

  • 後臺圖片解壓縮

  • 同一個 URL 不會重複下載

  • 自動識別無效 URL,不會反覆重試

  • 不阻塞主線程

  • 高性能

  • 使用 GCD 和 ARC

  • 支持多種圖片格式(包括 WebP 格式)

  • 支持動圖(GIF)

    • 4.0 之前的動圖效果並不是太好

    • 4.0 以後基於 FLAnimatedImage加載動圖

注:本文選讀的代碼是 3.7.3 版本的,所以動圖加載還不支持 FLAnimatedImage。

3. SDWebImage 與其他框架的對比

利益相關:以下兩篇文章都是 SDWebImage 的維護者所寫,具有一定的主觀性,僅供參考。

4. 常見問題

  • 問題 1:使用 UITableViewCell 中的 imageView 加載不同尺寸的網絡圖片時會出現尺寸縮放問題

解決方案:自定義 UITableViewCell,重寫 -layoutSubviews 方法,調整位置尺寸;或者直接棄用 UITableViewCell 的 imageView,自己添加一個 imageView 作爲子控件。

  • 問題 2:圖片刷新問題:SDWebImage 在進行緩存時忽略了所有服務器返回的 caching control 設置,並且在緩存時沒有做時間限制,這也就意味着圖片 URL 必須是靜態的了,要求服務器上一個 URL 對應的圖片內容不允許更新。但是如果存儲圖片的服務器不由自己控制,也就是說 圖片內容更新了,URL 卻沒有更新,這種情況怎麼辦?

解決方案:在調用 sd_setImageWithURL: placeholderImage: options:方法時設置 options 參數爲 SDWebImageRefreshCached,這樣雖然會降低性能,但是下載圖片時會照顧到服務器返回的 caching control。

  • 問題 3:在加載圖片時,如何添加默認的 progress indicator ?

解決方案:在調用 -sd_setImageWithURL:方法之前,先調用下面的方法:

  [imageView sd_setShowActivityIndicatorView:YES];

  [imageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];

5. 用法

5.1 UITableView 中使用 UIImageView+WebCache

UITabelViewCell 中的 UIImageView 控件直接調用 sd_setImageWithURL: placeholderImage:方法即可

5.2 使用回調 blocks

在 block 中得到圖片下載進度和圖片加載完成(下載完成或者讀取緩存)的回調,如果你在圖片加載完成前取消了請求操作,就不會收到成功或失敗的回調

1
2
3
4
5
     [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@ "http://www.domain.com/path/to/image.jpg" ]
                       placeholderImage:[UIImage imageNamed:@ "placeholder.png" ]
                              completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                                     ... completion code here ...
                                  }];

5.3 SDWebImageManager 的使用

UIImageView(WebCache) 分類的核心在於 SDWebImageManager 的下載和緩存處理,SDWebImageManager將圖片下載和圖片緩存組合起來了。SDWebImageManager也可以單獨使用。

1
2
3
4
5
6
7
8
9
10
11
     SDWebImageManager *manager = [SDWebImageManager sharedManager];
     [manager loadImageWithURL:imageURL
                       options:0
                      progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                             // progression tracking code
                      }
                      completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                         if  (image) {
                             // do something with image
                         }
                      }];

5.4 單獨使用 SDWebImageDownloader 異步下載圖片

我們還可以單獨使用 SDWebImageDownloader 來下載圖片,但是圖片內容不會緩存。

1
2
3
4
5
6
7
8
9
10
11
     SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
     [downloader downloadImageWithURL:imageURL
                              options:0
                             progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                 // progression tracking code
                             }
                            completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                                 if  (image && finished) {
                                     // do something with image
                                 }
                             }];

5.5 單獨使用 SDImageCache 異步緩存圖片

SDImageCache 支持內存緩存和異步的磁盤緩存(可選),如果你想單獨使用 SDImageCache 來緩存數據的話,可以使用單例,也可以創建一個有獨立命名空間的 SDImageCache 實例。

添加緩存的方法:

1
     [[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

默認情況下,圖片數據會同時緩存到內存和磁盤中,如果你想只要內存緩存的話,可以使用下面的方法:

1
     [[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey toDisk:NO];

讀取緩存時可以使用 queryDiskCacheForKey:done: 方法,圖片緩存的 key 是唯一的,通常就是圖片的 absolute URL。

1
2
3
4
     SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@ "myNamespace" ];
     [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
         // image is not nil if image was found
     }];

5.6 自定義緩存 key

有時候,一張圖片的 URL 中的一部分可能是動態變化的(比如獲取權限上的限制),所以我們只需要把 URL 中不變的部分作爲緩存用的 key。

1
2
3
4
     SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {
             url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
             return  [url absoluteString];
         };

6. SDWebImage 4.0 遷移指南

按照版本號慣例(Semantic Versioning),從版本號可以看出 SDWebImage 4.0 是一個大版本,在結構上和 API 方面都有所改動。

除了 iOS 和 tvOS 之外,SDWebImage 4.0 還支持更多的平臺——watchOS 和 Max OS X。

藉助 FLAnimatedImage 在動圖支持上做了改進,尤其是 GIF。

二、實現原理

1. 架構圖(UML 類圖)

SDWebImageClassDiagram.png

2. 流程圖(方法調用順序圖)

SDWebImageSequenceDiagram.png

3. 目錄結構

  • Downloader

    • SDWebImageDownloader

    • SDWebImageDownloaderOperation

  • Cache

    • SDImageCache

  • Utils

    • SDWebImageManager

    • SDWebImageDecoder

    • SDWebImagePrefetcher

  • Categories

    • UIView+WebCacheOperation

    • UIImageView+WebCache

    • UIImageView+HighlightedWebCache

    • UIButton+WebCache

    • MKAnnotationView+WebCache

    • NSData+ImageContentType

    • UIImage+GIF

    • UIImage+MultiFormat

    • UIImage+WebP

  • Other

    • SDWebImageOperation(協議)

    • SDWebImageCompat(宏定義、常量、通用函數)

微信圖片_20170511185123.png

4. 核心邏輯

下載 Source code(3.7.3),運行 pod install,然後打開 SDWebImage.xcworkspace,先 run 起來感受一下。

在瞭解細節之前我們先大概瀏覽一遍主流程,也就是最核心的邏輯。

我們從 MasterViewController 中的 [cell.imageView sd_setImageWithURL:url placeholderImage:placeholderImage]; 開始看起。

經過層層調用,直到 UIImageView+WebCache 中最核心的方法 sd_setImageWithURL: placeholderImage: options: progress: completed:。該方法中,主要做了以下幾件事:

  • 取消當前正在進行的加載任務 operation

  • 設置 placeholder

  • 如果 URL 不爲 nil,就通過 SDWebImageManager 單例開啓圖片加載任務 operation,SDWebImageManager 的圖片加載方法中會返回一個 SDWebImageCombinedOperation 對象,這個對象包含一個 cacheOperation 和一個 cancelBlock。

SDWebImageManager 的圖片加載方法 downloadImageWithURL:options:progress:completed: 中會先拿圖片緩存的 key (這個 key 默認是圖片 URL)去 SDImageCache 單例中讀取內存緩存,如果有,就返回給 SDWebImageManager;如果內存緩存沒有,就開啓異步線程,拿經過 MD5 處理的 key 去讀取磁盤緩存,如果找到磁盤緩存了,就同步到內存緩存中去,然後再返回給 SDWebImageManager。

如果內存緩存和磁盤緩存中都沒有,SDWebImageManager 就會調用 SDWebImageDownloader 單例的 -downloadImageWithURL: options: progress: completed: 方法去下載,該會先將傳入的 progressBlock 和 completedBlock 保存起來,並在第一次下載該 URL 的圖片時,創建一個  NSMutableURLRequest 對象和一個 SDWebImageDownloaderOperation 對象,並將該 SDWebImageDownloaderOperation 對象添加到 SDWebImageDownloader 的downloadQueue 來啓動異步下載任務。

SDWebImageDownloaderOperation 中包裝了一個 NSURLConnection 的網絡請求,並通過 runloop 來保持 NSURLConnection 在 start 後、收到響應前不被幹掉,下載圖片時,監聽 NSURLConnection 回調的 -connection:didReceiveData: 方法中會負責 progress 相關的處理和回調,- connectionDidFinishLoading: 方法中會負責將 data 轉爲 image,以及圖片解碼操作,並最終回調 completedBlock。

SDWebImageDownloaderOperation 中的圖片下載請求完成後,會回調給 SDWebImageDownloader,然後 SDWebImageDownloader 再回調給 SDWebImageManager,SDWebImageManager 中再將圖片分別緩存到內存和磁盤上(可選),並回調給 UIImageView,UIImageView 中再回到主線程設置 image 屬性。至此,圖片的下載和緩存操作就圓滿結束了。

當然,SDWebImage 中還有很多細節可以深挖,包括一些巧妙設計和知識點,接下來再看看SDWebImage 中的實現細節。

三、實現細節

注:爲了節省篇幅,這裏使用僞代碼的方式來解讀,具體的閱讀註解見 ShannonChenCHN/SDWebImage-3.7.3。

從上面的核心邏輯分析可以看出,SDWebImage 最核心的功能也就是以下 4 件事:

  • 下載(SDWebImageDownloader)

  • 緩存(SDImageCache)

  • 將緩存和下載的功能組合起來(SDWebImageManager)

  • 封裝成 UIImageView 等類的分類方法(UIImageView+WebCache 等)

1. 圖片下載

1.1 SDWebImageDownloader

SDWebImageDownloader 繼承於 NSObject,主要承擔了異步下載圖片和優化圖片加載的任務。

幾個問題

  • 如何實現異步下載,也就是多張圖片同時下載?

  • 如何處理同一張圖片(同一個 URL)多次下載的情況?

枚舉定義

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 下載選項
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
     SDWebImageDownloaderLowPriority = 1 << 0,
     SDWebImageDownloaderProgressiveDownload = 1 << 1,
     SDWebImageDownloaderUseNSURLCache = 1 << 2,
     SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
     SDWebImageDownloaderContinueInBackground = 1 << 4,
     SDWebImageDownloaderHandleCookies = 1 << 5,
     SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
     SDWebImageDownloaderHighPriority = 1 << 7,
};
 
// 下載任務執行順序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
     SDWebImageDownloaderFIFOExecutionOrder,  // 先進先出
     SDWebImageDownloaderLIFOExecutionOrder   // 後進先出
};

.h 文件中的屬性:

1
2
3
4
5
6
7
8
9
@property (assign, nonatomic) BOOL shouldDecompressImages;   // 下載完成後是否需要解壓縮圖片,默認爲 YES
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
 
@property (strong, nonatomic) NSString *username;
@property (strong, nonatomic) NSString *password;
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;

.m 文件中的屬性:

1
2
3
4
5
6
@property (strong, nonatomic) NSOperationQueue *downloadQueue;  // 圖片下載任務是放在這個 NSOperationQueue 任務隊列中來管理的
@property (weak, nonatomic) NSOperation *lastAddedOperation;
@property (assign, nonatomic) Class operationClass;
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;  // 圖片下載的回調 block 都是存儲在這個屬性中,該屬性是一個字典,key 是圖片的 URL,value 是一個數組,包含每個圖片的多組回調信息。用 JSON 格式表示的話,就是下面這種形式:

.h 文件中方法

1
2
3
4
5
6
7
8
9
10
11
12
13
+ (SDWebImageDownloader *)sharedDownloader;
 
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
 
- (void)setOperationClass:(Class)operationClass;  // 創建 operation 用的類
 
- (id)downloadImageWithURL:(NSURL *)url
                                          options:(SDWebImageDownloaderOptions)options
                                         progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                        completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
 
- (void)setSuspended:(BOOL)suspended;

.m 文件中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Lifecycle
+ (void)initialize;
+ (SDWebImageDownloader *)sharedDownloader;
- init;
- (void)dealloc;
 
// Setter and getter
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads;
- (NSUInteger)currentDownloadCount;
- (NSInteger)maxConcurrentDownloads;
- (void)setOperationClass:(Class)operationClass;
 
// Download
- (id)downloadImageWithURL:(NSURL *)url
                                          options:(SDWebImageDownloaderOptions)options
                                         progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                        completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
           andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                      forURL:(NSURL *)url
              createCallback:(SDWebImageNoParamsBlock)createCallback;
 
// Download queue            
- (void)setSuspended:(BOOL)suspended;

具體實現:

先看看 +initialize 方法,這個方法中主要是通過註冊通知 讓SDNetworkActivityIndicator 監聽下載事件,來顯示和隱藏狀態欄上的 network activity indicator。爲了讓 SDNetworkActivityIndicator 文件可以不用導入項目中來(如果不要的話),這裏使用了 runtime 的方式來實現動態創建類以及調用方法。

1
2
3
4
5
6
7
8
+ (void)initialize {
     if  (NSClassFromString(@ "SDNetworkActivityIndicator" )) {
         id activityIndicator = [NSClassFromString(@ "SDNetworkActivityIndicator" ) performSelector:NSSelectorFromString(@ "sharedActivityIndicator" )];
 
         # 先移除通知觀察者 SDNetworkActivityIndicator
         # 再添加通知觀察者 SDNetworkActivityIndicator
     }
}

+sharedDownloader 方法中調用了 -init 方法來創建一個單例,-init方法中做了一些初始化設置和默認值設置,包括設置最大併發數(6)、下載超時時長(15s)等。

1
2
3
4
5
6
7
8
- (id)init {
     #設置下載 operation 的默認執行順序(先進先出還是先進後出)
     #初始化 _downloadQueue(下載隊列),_URLCallbacks(下載回調 block 的容器),_barrierQueue(GCD 隊列)
     #設置 _downloadQueue 的隊列最大併發數默認值爲 6
     #設置 _HTTPHeaders 默認值 
     #設置默認下載超時時長 15s 
     ...
}

除了以上兩個方法之外,這個類中最核心的方法就是 - downloadImageWithURL: options: progress: completed: 方法,這個方法中首先通過調用 -addProgressCallback: andCompletedBlock: forURL: createCallback: 方法來保存每個 url 對應的回調 block,-addProgressCallback: ... 方法先進行錯誤檢查,判斷 URL 是否爲空,然後再將 URL 對應的 progressBlock 和 completedBlock 保存到 URLCallbacks 屬性中去。

URLCallbacks 屬性是一個 NSMutableDictionary 對象,key 是圖片的 URL,value 是一個數組,包含每個圖片的多組回調信息。用 JSON 格式表示的話,就是下面這種形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
     "callbacksForUrl1" : [
         {
             "kProgressCallbackKey" "progressCallback1_1" ,
             "kCompletedCallbackKey" "completedCallback1_1"
         },
         {
             "kProgressCallbackKey" "progressCallback1_2" ,
             "kCompletedCallbackKey" "completedCallback1_2"
         }
     ],
     "callbacksForUrl2" : [
         {
             "kProgressCallbackKey" "progressCallback2_1" ,
             "kCompletedCallbackKey" "completedCallback2_1"
         },
         {
             "kProgressCallbackKey" "progressCallback2_2" ,
             "kCompletedCallbackKey" "completedCallback2_2"
         }
     ]
}

這裏有個細節需要注意,因爲可能同時下載多張圖片,所以就可能出現多個線程同時訪問 URLCallbacks 屬性的情況。爲了保證線程安全,所以這裏使用了 dispatch_barrier_sync 來分步執行添加到 barrierQueue 中的任務,這樣就能保證同一時間只有一個線程能對 URLCallbacks 進行操作。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
     #1. 判斷 url 是否爲 nil,如果爲 nil 則直接回調 completedBlock,返回失敗的結果,然後 return,因爲 url 會作爲存儲 callbacks 的 key
 
     #2. 處理同一個 URL 的多次下載請求(MARK: 使用 dispatch_barrier_sync 函數來保證同一時間只有一個線程能對 URLCallbacks 進行操作):
       ## 從屬性 URLCallbacks(一個字典) 中取出對應 url 的 callBacksForURL(這是一個數組,因爲可能一個 url 不止在一個地方下載)
       ## 如果沒有取到,也就意味着這個 url 是第一次下載,那就初始化一個 callBacksForURL 放到屬性 URLCallbacks 中
       ## 往數組 callBacksForURL 中添加 包裝有 callbacks(progressBlock 和 completedBlock)的字典
       ## 更新 URLCallbacks 存儲的對應 url 的 callBacksForURL
 
     #3. 如果這個 url 是第一次請求下載,就回調 createCallback
 
}

如果這個 URL 是第一次被下載,就要回調 createCallback,createCallback 主要做的就是創建並開啓下載任務,下面是 createCallback 的具體實現邏輯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
      #1. 調用 - [SDWebImageDownloader addProgressCallback: andCompletedBlock: forURL: createCallback: ] 方法,直接把入參 url、progressBlock 和 completedBlock 傳進該方法,並在第一次下載該 URL 時回調 createCallback
 
         ## createCallback 的回調處理:{
           1.1 創建下載 request ,設置 request 的 cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining,以及 allHTTPHeaderFields(這個屬性交由外面處理,設計的比較巧妙)
 
           1.2 創建 SDWebImageDownloaderOperation(繼承自 NSOperation)
 
               ### 1.2.1 SDWebImageDownloaderOperation 的 progressBlock 回調處理 {
                         (這個 block 有兩個回調參數:接收到的數據大小和預計數據大小)
                          這裏用了 weak-strong dance
                          首先使用 strongSelf 強引用 weakSelf,目的是爲了保住 self 不被釋放  
                          然後檢查 self 是否已經被釋放(這裏爲什麼先「保活」後「判空」呢?因爲如果先判空的話,有可能判空後 self 就被釋放了)
                          取出 url 對應的回調 block 數組(這裏取的時候有些講究,考慮了多線程問題,而且取的是 copy 的內容)
                          遍歷數組,從每個元素(字典)中取出 progressBlock 進行回調       
                      }
               ### 1.2.2 SDWebImageDownloaderOperation 的 completedBlock 回調處理 {
                          (這個 block 有四個回調參數:圖片 UIImage,圖片數據 NSData,錯誤 NSError,是否結束 isFinished)
                          同樣,這裏也用了 weak-strong dance
                          接着,取出 url 對應的回調 block 數組
                          如果結束了(isFinished),就移除 url 對應的回調 block 數組(移除的時候也要考慮多線程問題)
                          遍歷數組,從每個元素(字典)中取出 completedBlock 進行回調 
               }
               ### SDWebImageDownloaderOperation 的 cancelBlock 回調處理 {
                          同樣,這裏也用了 weak-strong dance
                          然後移除 url 對應的所有回調 block
               }
           1.3 設置下載完成後是否需要解壓縮
           1.4 如果設置了 username 和 password,就給 operation 的下載請求設置一個 NSURLCredential  
           1.5 設置 operation 的隊列優先級
           1.6 將 operation 加入到隊列 downloadQueue 中,隊列(NSOperationQueue)會自動管理 operation 的執行
           1.7 如果 operation 執行順序是先進後出,就設置 operation 依賴關係(先加入的依賴於後加入的),並記錄最後一個 operation(lastAddedOperation)
         }
 
      #2. 返回 createCallback 中創建的 operation(SDWebImageDownloaderOperation)
}

createCallback 方法中調用了 - [SDWebImageDownloaderOperation initWithRequest: options: progress:] 方法來創建下載任務 SDWebImageDownloaderOperation。那麼,這個 SDWebImageDownloaderOperation 類究竟是幹什麼的呢?下一節再看。

知識點:

1.SDWebImageDownloaderOptions 枚舉使用了位運算

應用:通過「與」運算符,可以判斷是否設置了某個枚舉選項,因爲每個枚舉選擇項中只有一位是1,其餘位都是 0,所以只有參與運算的另一個二進制值在同樣的位置上也爲 1,與 運算的結果纔不會爲 0.

1
2
3
   0101 (相當於 SDWebImageDownloaderLowPriority | SDWebImageDownloaderUseNSURLCache)
& 0100 (= 1 << 2,也就是 SDWebImageDownloaderUseNSURLCache)
= 0100 (> 0,也就意味着 option 參數中設置了 SDWebImageDownloaderUseNSURLCache)

2.dispatch_barrier_sync 函數的使用

3.weak-strong dance

4.HTTP header 的理解

5.NSOperationQueue 的使用

6.NSURLRequest 的 cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining

7.NSURLCredential

8.createCallback 裏面爲什麼要用 wself?

1
NSTimeInterval timeoutInterval = wself.downloadTimeout;

1.2 SDWebImageDownloaderOperation

每張圖片的下載都會發出一個異步的 HTTP 請求,這個請求就是由 SDWebImageDownloaderOperation 管理的。

SDWebImageDownloaderOperation 繼承 NSOperation,遵守 SDWebImageOperation、NSURLConnectionDataDelegate 協議。

SDWebImageOperation 協議只定義了一個方法 -cancel,用來取消 operation。

幾個問題

  • 如何實現下載的網絡請求?

  • 如何管理整個圖片下載的過程?

  • 圖片下載完成後需要做哪些處理?

.h 文件中的屬性:

1
2
3
4
5
6
7
8
9
@property (strong, nonatomic, readonly) NSURLRequest *request;  // 用來給 operation 中的 connection 使用的請求
@property (assign, nonatomic) BOOL shouldDecompressImages;  // 下載完成後是否需要解壓縮
@property (nonatomic, assign) BOOL shouldUseCredentialStorage; 
@property (nonatomic, strong) NSURLCredential *credential;
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
@property (assign, nonatomic) NSInteger expectedSize;
@property (strong, nonatomic) NSURLResponse *response;
 
其他繼承自 NSOperation 的屬性(略)

.m 文件中的屬性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;    
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
 
@property (assign, nonatomic, getter = isExecuting) BOOL executing;  // 覆蓋了 NSOperation 的 executing
@property (assign, nonatomic, getter = isFinished) BOOL finished;   // 覆蓋了 NSOperation 的 finished
@property (assign, nonatomic) NSInteger expectedSize;
@property (strong, nonatomic) NSMutableData *imageData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, atomic) NSThread *thread;
 
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;  // Xcode 的 BaseSDK 設置爲 iOS 4.0 時以上使用
 
// 成員變量
size_t width, height;                   // 圖片寬高
UIImageOrientation orientation;       // 圖片方向
BOOL responseFromCached;

.h 文件中的方法:

1
2
3
4
5
6
7
- (id)initWithRequest:(NSURLRequest *)request
               options:(SDWebImageDownloaderOptions)options
              progress:(SDWebImageDownloaderProgressBlock)progressBlock
             completed:(SDWebImageDownloaderCompletedBlock)completedBlock
             cancelled:(SDWebImageNoParamsBlock)cancelBlock;    
 
其他繼承自 NSOperation 的方法(略)

.m 文件中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 覆蓋了父類的屬性,需要重新實現屬性合成方法
@synthesize executing = _executing;
@synthesize finished = _finished;
 
// Initialization
- (id)initWithRequest:(NSURLRequest *)request
               options:(SDWebImageDownloaderOptions)options
              progress:(SDWebImageDownloaderProgressBlock)progressBlock
             completed:(SDWebImageDownloaderCompletedBlock)completedBlock
             cancelled:(SDWebImageNoParamsBlock)cancelBlock;
// Operation
- (void)start;
- (void)cancel;
- (void)cancelInternalAndStop;
- (void)cancelInternal;
- (void)done;
- (void)reset;
 
// Setter and getter
- (void)setFinished:(BOOL)finished; 
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
// Operation
- (void)start;
- (void)cancel;
- (void)cancelInternalAndStop;
- (void)cancelInternal;
- (void)done;
- (void)reset;
 
// Setter and getter
- (void)setFinished:(BOOL)finished; 
- (void)setExecuting:(BOOL)executing; 
- (BOOL)isConcurrent; 
 
// NSURLConnectionDataDelegate 方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;  //  下載過程中的 response 回調
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;  // NSURLConnectionDataDelegate 方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;  //  下載過程中的 response 回調
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;  // 下載過程中 data 回調
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection;  // 下載完成時回調
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;  // 下載失敗時回調
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;  // 在 connection 存儲 cached response 到緩存中之前調用
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection;  //  URL loader 是否應該使用 credential storage
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;  // connection 發送身份認證的請求之前被調用
 
// Helper
//  下載過程中的 response 回調
相關文章
相關標籤/搜索