SDWebImage 加載圖片原理


目前標註的類

通過標註的類,看懂應該沒問題了。

SDWebImage是一個圖片緩存的框架。相較於AFNetworking集成的UIImageView+AFNetworking.h,對於圖片的緩存實際應用的是NSURLCache自帶的cache機制。而NSURLCache每次都要把緩存的raw data 再轉化爲UIImage,就帶來了數據處理和內存方面的更多操作。SDWebImage的緩存由SDImageCache類來實現,這是一個單例類,該類負責處理內存緩存及一個可選的磁盤緩存,其中磁盤緩存的寫操作是異步的,這樣就不會對UI操作造成影響。此外還提供了若干屬性和接口來配置和操作緩存對象。包含以下功能:

1.提供UIImageView的一個分類,以支持網絡圖片的加載與緩存管理

2.一個異步的圖片加載器

3.一個異步的內存+磁盤圖片緩存

4.支持GIF圖片

5.支持WebP圖片

6.後臺圖片解壓縮處理

7.確保同一個URL的圖片不被下載多次

8.確保虛假的URL不會被反覆加載

9.確保下載及緩存時,主線程不被阻塞

SDWebImage底層實現原理:

SDWebImage有沙盒緩存機制,主要由三塊組成

1.內存圖片緩存

2.內存操作緩存

3.磁盤沙盒緩存

SDWebImage的大部分工作是由緩存對象SDImageCache和異步下載器管理對象SDWebImageManager來完成的。SDWebImage的圖片下載是由SDWebImageDownloader這個類來實現的,它是一個異步下載管理器,下載過程中增加了對圖片加載做了優化的處理。而真正實現圖片下載的是自定義的一個Operation操作,將該操作加入到下載管理器的操作隊列downloadQueue中,Operation操作依賴系統提供的NSURLConnection類實現圖片的下載。

SDWebImage提供了對圖片緩存的支持,而該功能是由SDImageCache類來完成的。該類負責處理內存緩存及一個可選的磁盤緩存。內存緩存的處理是使用NSCache對象來實現的。NSCache是一個類似於集合的容器。它存儲key-value對,這一點類似於NSDictionary類,用搜索文件系統的方式做管理,文件替換方式是以時間爲單位。我們通常用使用緩存來臨時存儲短時間使用但創建昂貴的對象。重用這些對象可以優化性能,因爲它們的值不需要重新計算。另外一方面,這些對象對於程序來說不是緊要的,在內存緊張時會被丟棄。

磁盤緩存的處理則是使用NSFileManager對象來實現的。圖片存儲的位置是位於Cache文件夾。另外,SDImageCache還定義了一個串行隊列,來異步存儲圖片。

當SDWebImageManager向SDImageCache要資源時,先搜索內存層面的數據,如果有直接返回,沒有的話去訪問磁盤,將圖片從磁盤讀取出來,然後做Decoder,將圖片對象放到內存層面做備份,再返回調用層。使用Decoder 是因爲UIImage的imageWithData函數是每次畫圖的時候纔將Data解壓成ARGB的圖像,
所以在每次畫圖的時候,會有一個解壓操作,這樣效率很低,但是隻有瞬時的內存需求。
爲了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓,然後畫在另外一張圖片上,這樣這張新圖片就不再需要重複解壓了。是典型的空間換時間的做法。


SDWebImage的原理



1.使用

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;

會先把 placeholderImage 顯示,然後 SDWebImageManager 根據 URL 開始處理圖片。

2.進入 SDWebImageManager

-downloadWithURL:options:progress:completed:

交給 SDImageCache 從緩存查找圖片是否已經下載。

3.先從內存圖片緩存查找是否有圖片,如果內存中已經有圖片緩存,取緩存,沒有從- (UIImage )diskImageForKey:(NSString )key去磁盤緩存中去查找,根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。在磁盤緩存中找到後,同時更新置內存緩存中(如果空閒內存過小,會先清空內存緩存),有回調則調用doneBlock回調。

4.找到了就從SDWebImageQueryCompletedBlock到 UIImageView+WebCache 等前端展示圖片。

5.如果從硬盤緩存目錄讀取不到圖片,說明不存在該圖片,需要下載圖片,共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。 圖片下載由 NSURLSession 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。

6.URLSession:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。數據下載完成後交給 SDWebImageDecoder 做圖片解碼處理。

7.圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這裏完成,效率會好很多。

8.在主線程 SDWebImageDownloaderCompletedBlock裏處理解碼完成後的操作。回調給需要的地方展示圖片。

9.從SDWebImageDownloaderProgressBlock 回調給 SDWebImageManager 告知圖片下載信息。

10.將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。

11.SDImageCache 在初始化的時候會註冊一些消息通知,在內存警告或退到後臺的時候清理內存圖片緩存,應用結束的時候清理過期圖片。

12.SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache方便使用。 SDWebImagePrefetcher 可以預先下載圖片,方便後續使用。


SDWebImage 使用

常用到的對象:

1、UIImageView (WebCache)類別,入口封裝,實現讀取圖片完成後的回調。

2、SDWebImageManager,對圖片進行管理的中轉站,記錄那些圖片正在讀取。
向下層讀取Cache(調用SDImageCache),或者向網絡讀取對象(調用SDWebImageDownloader) 。
實現SDImageCache和SDWebImageDownloader的回調。

3、SDImageCache,根據URL的MD5摘要對圖片進行存儲和讀取(實現存在內存中或者存在硬盤上兩種實現)
實現圖片和內存清理工作。

4、SDWebImageDownloader,根據URL向網絡讀取數據(實現部分讀取和全部讀取後再通知回調兩種方式)

5、SDWebImageDecoder,異步對圖像進行了一次解壓

使用:

[self.imageView sd_setImageWithURL:self.imageURL
                      placeholderImage:nil
                               options:SDWebImageProgressiveDownload
                              progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                  ...
                              }
                             completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                                 ...
                             }];

調用setImageWithURL:方法的時候,SDWebImage自動做很多事,當你需要在某一具體時刻做事情的時候,你可以覆蓋這些方法。比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:

//這個方法是下載imagePath2的時候響應
SDWebImageManager *manager = [SDWebImageManager sharedManager];

[manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {

    NSLog(@"顯示當前進度");

} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

    NSLog(@"下載完成");
}];

基本代碼:

使用SDWebImageManager類:可以進行一些異步加載的工作。

SDWebImageManager *manager = [SDWebImageManager sharedManager];
UIImage *cachedImage = [manager imageWithURL:url]; // 將需要緩存的圖片加載進來
if (cachedImage) {
  // 如果Cache命中,則直接利用緩存的圖片進行有關操作
  // Use the cached image immediatly
} else {
  // 如果Cache沒有命中,則去下載指定網絡位置的圖片,並且給出一個委託方法
  // Start an async download
 [manager downloadWithURL:url delegate:self];
}

當然你的類要實現SDWebImageManagerDelegate協議,並且要實現協議的webImageManager:didFinishWithImage:方法。

// 當下載完成後,調用回調方法,使下載的圖片顯示
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image {
// Do something with the downloaded image
}

獨立的異步圖像下載

可能會單獨用到異步圖片下載,則一定要用downloaderWithURL:delegate:來建立一個SDWebImageDownloader實例。

downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self];

這樣SDWebImageDownloaderDelegate協議的方法imageDownloader:didFinishWithImage:被調用時下載會立即開始並完成。

獨立的異步圖像緩存

SDImageCache類提供一個創建空緩存的實例,並用方法imageForKey:來尋找當前緩存。

UIImage *myCachedImage = [[SDImageCache sharedImageCache] imageFromKey:myCacheKey];

存儲一個圖像到緩存是使用方法storeImage: forKey:

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

默認情況下,圖像將被存儲在內存緩存和磁盤緩存中。如果僅僅是想內存緩存中,要使用storeImage:forKey:toDisk:方法的第三個參數帶一負值
來替代。

SDWebImage 源碼分析示例

SDWebImageDownloader類

SDWebImageDownloaderOptions定義:

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,

// 默認情況下請求不使用NSURLCache,如果設置該選項,則以默認的緩存策略來使用NSURLCache
SDWebImageDownloaderUseNSURLCache = 1 << 2,

// 如果從NSURLCache緩存中讀取圖片,則使用nil作爲參數來調用完成block
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,

// 在iOS 4+系統上,允許程序進入後臺後繼續下載圖片。該操作通過向系統申請額外的時間來完成後臺下載。如果後臺任務終止,則操作會被取消
SDWebImageDownloaderContinueInBackground = 1 << 4,

// 通過設置NSMutableURLRequest.HTTPShouldHandleCookies = YES來處理存儲在NSHTTPCookieStore中的cookie
SDWebImageDownloaderHandleCookies = 1 << 5,

// 允許不受信任的SSL證書。主要用於測試目的。
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

// 將圖片下載放到高優先級隊列中
SDWebImageDownloaderHighPriority = 1 << 7,
};

下載順序:

typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
// 以隊列的方式,按照先進先出的順序下載。這是默認的下載順序
SDWebImageDownloaderFIFOExecutionOrder,
// 以棧的方式,按照後進先出的順序下載。
SDWebImageDownloaderLIFOExecutionOrder
};

每個下載操作都定義了回調操作,如下載進度回調,下載完成回調,頭部過濾等,這些回調操作是以block形式來呈現;每個下載操作的下載進度回調和下載完成回調,這兩個回調稍後將保存在下載管理器的URLCallbacks字典中,key爲URL,value爲一個數組,數組裏面又存放一個保存了下載進度回調和完成回調代碼塊的字典。這個字典數組同時也保證了同一張圖片只會被下載一次。

// 下載進度
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
// 下載完成
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
// Header過濾
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);

爲了保證URLCallbacks操作(添加、刪除)的線程安全性,SDWebImageDownloader將這些操作作爲一個個任務放到barrierQueue隊列中,並設置屏障來確保同一時間只有一個線程操作URLCallbacks屬性。

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
...
// 1. 以dispatch_barrier_sync操作來保證同一時間只有一個線程能對URLCallbacks進行操作
dispatch_barrier_sync(self.barrierQueue, ^{
...
// 2. 處理同一URL的同步下載請求的單個下載
});
}

下載請求的管理都是放在downloadImageWithURL:options:progress:completed:方法裏面來處理的,該方法調用了上面所提到的addProgressCallback:andCompletedBlock:forURL:createCallback:方法來將請求的信息存入管理器中,同時在創建回調的block中創建新的操作,配置之後將其放入downloadQueue操作隊列中,最後方法返回新創建的操作。

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
...
[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
...
}

下載操作的超時時間可以通過downloadTimeout屬性來設置,默認值爲15秒。

SDWebImage定義了一個協議,即 SDWebImageOperation 作爲圖片下載操作的基礎協議。它只聲明瞭一個cancel方法,用於取消操作。每個圖片的下載都是一個Operation操作。SDWebImage自定義了一個Operation類,即 SDWebImageDownloaderOperation ,它繼承自NSOperation,並採用了SDWebImageOperation協議。除了繼承而來的方法,該類只向外暴露了一個方法,initWithRequest:options:progress:completed:cancelled:。對於圖片的下載,SDWebImageDownloaderOperation完全依賴於URL加載系統中的NSURLSession。具體看代碼源碼分析.

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

方法的主要任務是接收數據。每次接收到數據時,都會用現有的數據創建一個CGImageSourceRef對象以做處理。在首次獲取到數據時(width+height==0)會從這些包含圖像信息的數據中取出圖像的長、寬、方向等信息以備使用。而後在圖片下載完成之前,會使用CGImageSourceRef對象創建一個圖片對象,經過縮放、解壓縮操作後生成一個UIImage對象供完成回調使用。當然,在這個方法中還需要處理的就是進度信息。如果我們有設置進度回調的話,就調用這個進度回調以處理當前圖片的下載進度。

縮放操作可以查看SDWebImageCompat文件中的SDScaledImageForKey函數;解壓縮操作可以查看SDWebImageDecoder文件+decodedImageWithImage方法。在下載完成或下載失敗後,需要停止當前線程的run loop,清除連接,並拋出下載停止的通知。如果下載成功,則會處理完整的圖片數據,對其進行適當的縮放與解壓縮操作,以提供給完成回調使用。

最重要是自己分析,看過的會忘,消化了纔是自己的。學而不思則罔,思而不學則殆。每個人要了解自己的優缺點。有思考纔有所得!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章