SDWebImage 原始碼分析

Karepbq發表於2019-05-09

主要圍繞三個方面來闡述

  • 載入流程
  • 快取模組
  • 下載模組

載入流程

SDWebImage 原始碼分析
這是從 github SDWebImage 地址下載的圖. 圖片說明了整個圖片載入的時序.

  1. 首先是根據地址去快取中取圖片,此處的快取是雙快取(磁碟 + 記憶體)
  2. 如果找到了就交付到上層處理/顯示
  3. 如果沒有都沒找到,就會去網路下載圖片
  4. 下載完了交付上層處理/顯示
  5. 另外對下載的圖片進行快取

下面根這原始碼來驗證一下上面說的知識 根據 Github 上面提供的整個 SD 的類圖可知,主要是 SDWebImageManager 在操作管理查詢流程. 我們就根據大家使用的最多的方法, 來一步步跟蹤.

  1. 通常情況下,我們使用這個方法對圖片進行載入。
UIImageView+WebCache.h 檔案中
-(void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
複製程式碼
  1. 查詢 sd_internalSetImageWithURL 方法.
UIView+WebCache.h 檔案中
sd_internalSetImageWithURL ....
複製程式碼
  1. 如果你設定了 PlaceholderImage, 他會先去顯示你的 placeholderImage
if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
複製程式碼
  1. 繼續去 SDWebImageManager 中的 loadImageWithURL 查詢圖片
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
複製程式碼
  1. 然後呼叫 callCacheProcessForOperation 進行快取查詢操作
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
	// 快取查詢
}else {
	// 進行下載操作
}
複製程式碼
  1. 接上快取查詢過程, 回去 SDImageCache 中的 queryImageForKey 方法繼續快取查詢
-(id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
複製程式碼
  1. 接下來到 queryCacheOperationForKey 方法

    • 首先去記憶體中找,如果有就用 Block 的方式傳遞到上層顯示
    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    複製程式碼
    • 否則就去 Disk 查詢
    NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
    複製程式碼
    • 如果 Disk 有就返回,並且把這個值存入 MemoryCache 中,這樣就可以再下次查詢中更快的找到對應的圖片資訊
    [self.memCache setObject:diskImage forKey:key cost:cost];
    複製程式碼
  2. 如果沒找到就去下載圖片

callDownloadProcessForOperation
複製程式碼
  1. 進入下載流程, 生成 SDWebImageDownloadToken 物件.就行下載
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
複製程式碼
  1. 下載完成就行儲存
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
複製程式碼
* 分別儲存到記憶體和磁碟
```
-(void)storeImage:(nullable UIImage *)image
     imageData:(nullable NSData *)imageData
        forKey:(nullable NSString *)key
      toMemory:(BOOL)toMemory
        toDisk:(BOOL)toDisk
    completion:(nullable SDWebImageNoParamsBlock)completionBlock {
``` 
複製程式碼
  1. 返回資料顯示
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
複製程式碼

到此整個圖片載入流程就完了.

SDWebImage 原始碼分析


快取模組

下圖是最新的 SDWebImage 的檔案結構,這裡主要看看 Cache 板塊

SDWebImage 原始碼分析

  • 首先介紹這些檔案是幹啥的(記得之前的 Cache 沒這麼多檔案)
    • SDImageCacheConfig.h SDImageCacheConfig.m 這兩個檔案是來做一些設定
    • SDMemoryCache.h SDMemoryCache.m 是記憶體快取相關的操作
    • SDDiskCache.h SDDiskCache.m 是磁碟快取相關的操作
    • SDImageCacheDefine.h SDImageCacheDefine.m 主要是為 SDImageCache 定義了一些協議
    • SDImageCachesManager.h SDImageCachesManager.m 主要對 Cache 刪除和新增
    • SDImageCache.h SDImageCache.m 操作 SDDiskCacheSDMemoryCache
首先先看設定檔案 SDImageCacheConfig
* 定義了預設的磁碟快取時間, 預設為 1 周 `kDefaultCacheMaxDiskAge = 60 * 60 * 24 *7;`
* shouldDisableiCloud 是否使用 iCloud
* shouldCacheImagesInMemory 是否使用記憶體快取
* shouldUseWeakMemoryCache 是否使用弱應用記憶體快取
* shouldRemoveExpiredDataWhenEnterBackground 是否刪除超過日期的資料
* maxDiskAge 最大磁碟快取週期為 `1` 周
* maxDiskSize 最大磁碟快取大小.預設為 0,意味著沒有大小限制
* maxMemoryCost 記憶體快取大小,預設為 0,意味著沒有大小限制
* maxMemoryCount 記憶體快取的最大數量,預設 0
* diskCacheExpireType  磁碟快取失效的型別(用於刪除使用)
複製程式碼
SDMemoryCache
* `SDMemoryCache` 是繼承自 系統的 `NSCache`.
* 這個類主要是操作記憶體,刪除,儲存的操作
* 首先註冊了記憶體警告通知 `UIApplicationDidReceiveMemoryWarningNotification`
* 儲存
	- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
		[super setObject:obj forKey:key cost:g];
		if (!self.config.shouldUseWeakMemoryCache) {
    		return;
	    }
	    if (key && obj) {
        // Store weak cache
    		SD_LOCK(self.weakCacheLock);
        [self.weakCache setObject:obj forKey:key];
	      SD_UNLOCK(self.weakCacheLock);
	    }
	}
	
	Note: 這裡的儲存方法是做了兩次儲存操作
		1. 使用 [Super setObject:obj forKey:key cost: g] 先給父類設定,也就是給系統的 NSCache 設定快取
		2. 然後在給當前快取內的 `NSMapTable` 設定值
		3. 這樣也就是拿空間換時間的操作,相當於在記憶體中是有兩份資料的,如果第一份沒有,就去拿第二份,這樣就保證了快速度的響應. 

* 取值
	-(id)objectForKey:(id)key {
		 id obj = [super objectForKey:key];
	    if (!self.config.shouldUseWeakMemoryCache) {
		      return obj;
	    }
		  if (key && !obj) {
 		   // Check weak cache
 	   	SD_LOCK(self.weakCacheLock);
     	   obj = [self.weakCache objectForKey:key];
   	   SD_UNLOCK(self.weakCacheLock);
   	   if (obj) {
          // Sync cache
          NSUInteger cost = 0;
          if ([obj isKindOfClass:[UIImage class]]) {
              cost = [(UIImage *)obj sd_memoryCost];
          }
          [super setObject:obj forKey:key cost:cost];
      }
	    }
  	  return obj;
	}
	1. 先去 `super` 取值
	2. 如果木有就去 `NSMapTable` 中取值
複製程式碼
SDDiskCache
* 使用 NSFileManager 管理圖片快取
* 根據 `key` 在它 SDDiskCacheFileNameForKey 方法中,使用 `CC_MD5` 生成一個 新的  `Key`, 轉換成 url,存放 data.
* 還有一點就是清理過期的資料,有兩種方式
	1. SDImageCacheConfigExpireTypeAccessDate 根據訪問時間
	2. SDImageCacheConfigExpireTypeModificationDate 修改時間(預設)
	3. 根據 self.config.maxDiskAge 來對比刪除超過時間的圖片
	4. 根據 self.config.maxDiskSize 來刪除磁碟快取的資料,清理到self.config.maxDiskSize /2 為止.
複製程式碼
SDImageCachesManager
* 主要是操作 `SDImageCache` 類,對快取就行 `儲存`, `刪除`, `清理`, `查詢`
複製程式碼
SDImageCacheConfig
* 主要是對圖片的 `Decode` 操作
複製程式碼
SDImageCache
* 值得說的是,他在這裡監聽了兩個系統通知
1. UIApplicationWillTerminateNotification
2. UIApplicationDidEnterBackgroundNotification
當這兩個方法執行時,其實是做了一件事情,就是清理磁碟快取 
[self.diskCache removeExpiredData];
複製程式碼

下載模組

SDWebImageDownloaderConfig
* 設定最大併發數 maxConcurrentDownloads ,預設是 6
* 設定下載超時時間 downloadTimeout ,預設 15s
* 下載執行順序 executionOrder , 預設先進先出
複製程式碼
SDWebImageDownloader
Note: 總結就是 使用 系統 NSURLSession 建立 task ,在本類中它實現了 NSURLSessionTaskDelegate, 當資料下載完成,就去顯示.
	* 主要是設定下載佇列
  	* 拼接 HTTPHeader 
	* 組裝 SDWebImageDownloaderOperation 
  	* 設定 Credential
	* 配置 NSOperation 的優先順序
	* 返回一個 SDWebImageDownloaderOperation
	* 雖然設定了 NSURLSessionTaskDelegate 的代理在這個類中,但是實際還是把結果交費給了 SDWebImageDownloaderOperation, 統一處理
	
	NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
    		[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
	 } else {
  	  if (completionHandler) {
     	   completionHandler(NSURLSessionResponseAllow);
   		 }
	}
複製程式碼
SDWebImageDownloaderOperation
Note: 他是整合自 NSOperation, 重寫了 Start 方法
	* 在 Start 時,如果 app 進入後臺,就取消下載.
	* 設定 NSURLSession 之後就下載任務
	* 其中手動(KVC)出發了 isFinished isExecuting
	* 然後就是在 NSURLSessionTaskDelegate,NSURLSessionDataDelegate,設定顯示就回撥
複製程式碼

相關文章