SDWebImage 筆記

kim_jin發表於2017-12-26

此為SDWebImage的原始碼閱讀筆記


  1. 使用GCD時,如果需要比較兩個執行緒是否相等的話,可以使用獲取執行緒的label,然後比較的方法:
// SDWebImageCompat.h
// L104
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
  //...
}
複製程式碼

上面的程式碼會判斷當前的執行緒是否為主執行緒,因為對於UIImageView的更新的話需要發生在主執行緒上面。


Downloader

從模組名稱我們能看出,這個模組是用於圖片下載的。

  1. 下載選項
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
  // 預設模式,
  SDWebImageDownloaderLowPriority = 1 << 0;
  // 漸進式下載,每接收到一段資料就會進行返回,可以一點一點顯示圖片
  SDWebImageDownloaderProgressiveDownload = 1 << 1;
  // 使用此標誌的話,使用NSURLCache
  SDWebImageDownloadUseNSURLCache = 1 << 2;
  // 如果圖片是從NSURLCache中讀取的話,在completion block中使用的圖片引數資料是nil
  SDWebImageDownloaderIgnoreCachedResponse = 1 << 3;
  // 允許在app進入後臺後,繼續圖片的下載
  SDWebImageDownloaderContinueInBackground = 1 << 4;
  // 處理NSHTTPCookieStore中的cookies
  SDWebImageDownloaderHandleCookies = 1 << 5;
  // 允許使用不被信任的證書 SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6;
  // 將圖片的下載放到優先順序比較高的執行緒
  SDWebImageDownloaderHighPriority = 1 << 7;
  // 縮小圖片
  SDWebImageDownloaderScaleDownLargeImages = 1 << 8;
}
複製程式碼
  1. SDWebImage提供了兩種圖片的下載順序,分別是SDWebImageDownloadFIFOExecutionOrderSDWebImageDownloaderLIFOExecutionOrder。不難從名稱中看出下載佇列中圖片的執行順序。

  2. SDWebImageDownloadToken:每個下載都有一個token引數,可以根據token引數的值來取消對應的下載

  3. SDWebImageDownloader是一個同步圖片下載和優化器(挑幾個自己覺得重要的屬性和方法)

  • shouldDecompressImage: 一個布林值,預設是YES。表明在下載和快取圖片的過程中是否對圖片進行解壓操作,雖然可以優化圖片的顯示,但是會話費較多的記憶體。所以在記憶體不足的情況下,可以關閉這個屬性
  • maxConcurrentDownloads:最大的並行下載數,預設值是6
  • downloadTimeout: 下載的超時時長,預設為15s
  1. SDWebImage內,在下載過程中,下載使用的執行緒是NSOperationNSOperationQueue組合,而處理下載操作的處理時,還有用到了GCD佇列

  2. 下載器的session初始化:

self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                   delegate:self
                                              delegateQueue:nil];
複製程式碼

delegate的處理佇列傳入的引數值是nil,傳入nil的話,可以保證session建立一個序列佇列來處理方法的呼叫。會根據圖片的下載順序,對佇列中的操作新增依賴

  1. 在根據token的取值取消圖片下載的時候,通過dispatch_barrier_async對操作佇列進行barrier的操作

  2. 在新增回撥block的時候,會在barrierQueue中進行同步操作,設定token相關的引數,並新增到操作字典URLOperations中。(SDWebImageDownloader - `addProgressCallback:completedBlock:forURL:createCallback:)

  3. SDWebImage中,通過繼承NSOperation寫了一個SDWebImageDownloaderOperation用於下載執行緒的管理。 在NSOperation中,作者將必要實現的方法抽象出來變成SDWebImageDownloaderOperationInterface,如果需要高度定製下載圖片的話,需要實現介面的方法

  4. SDWebImageDownloaderOperation是一個併發操作

  5. (SDWebImageDownloaderOperation.m - L308)使用CGImageSourceCreateWithData將獲取到的資料轉化為CGImageSourceRef型別(作為data source),在每次獲取到新的資料時,都需要拼接到原有的資料上,再傳入到方法中,而不是隻傳入新的資料。

  6. (SDWebImageDownloaderOperation.m - L310 ~ L330)當通過NSURLSession的delegate獲取到資料後,如果當前的圖片寬和高資訊均為0的話,則通過CGImageSourceCopyPropertiesAtIndex()方法獲取到CFDictionaryRef的字典,根據kCGImagePropertyPixelHeight, kCGImagePropertyPixelWidthkCGImagePropertyOrientation可以獲取到圖片的寬,高和方向這三個值(獲取圖片方向的原因是因為通過Core Graphics繪製時,會丟失方向的引數)

  7. (SDWebImageDownloaderOperation.m - L332 ~ L377)使用CGImageSourceCreateImageAtIndex()來建立CGImage型別的image例項(記住要使用CGImageRelease()進行釋放),接下來就是使用Core Graphics框架進行圖片的繪製,關鍵的方法CGBitmapContextCreate()。關於使用Core Graphics進行圖片繪製的相關知識的話,可以參考官網文件。這裡就不詳細介紹了:developer.apple.com/library/con…。當繪製成功之後,轉化為UIImage物件,使用imageWithCGImage:scale:orientation來傳入方向引數。最後根據需求對圖片進行壓縮。


Cache

Cache是SDWebImage用於快取的模組。SDImageCache繼承自NSObject,有記憶體快取和硬碟快取兩種方式。對於硬碟快取的話,在進行寫操作的時候使用的是非同步寫入的方式。

  1. 圖片的預設快取時長為1周時間:`static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

  2. 在進行圖片快取的時候,每張圖片都有一個唯一的key值,以便對快取進行查詢,key值通常是圖片的絕對路徑

  3. 在進行快取時,使用的是一個序列的IO佇列來進行寫入

  4. 在對SDImageCache進行初始化的時候,註冊監聽了三個通知:

  • 在接收到記憶體警告UIApplicationDidReceiveMemoryWarningNotification的時候,會清空記憶體快取
  • 在app將要退出UIApplicationWillTerminateNotification的時候,會刪除舊檔案
  • 在進入後臺UIApplicationDidEnterBackgroundNotification時,會在後臺執行緒中刪除舊檔案
  1. 快取時,會將圖片的路徑(即key值)進行MD5取值作為快取的檔名
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
  const char *str = key.UTF8String;
  if (str == NULL) {
      str = "";
  }
  unsigned char r[CC_MD5_DIGEST_LENGTH];
  CC_MD5(str, (CC_LONG)strlen(str), r);
  NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                        r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                        r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];

  return filename;
}
複製程式碼
  1. 硬碟快取的儲存路徑是在'Library/Caches'路徑下。預設情況下,如果沒有取消快取策略的話,都會在記憶體快取中(NSCache *memCache)儲存一份,對於硬碟儲存的則是可選項.

  2. 從快取獲取圖片的時候,首先會檢查記憶體快取,如果記憶體快取中沒有這張圖片的話,就會檢查硬碟快取,如果硬碟快取中沒有,就會開始下載。但是,如果在硬碟快取中命中的話,在返回圖片的同時,會將圖片快取到記憶體中,提高查詢效率。 想要移除圖片的話,首先移除記憶體快取中的資料,然後再移除硬碟快取中的。 在SDWebImage中,對硬碟快取的操作都是在ioQueue的GCD佇列中進行非同步操作,然後返回到主執行緒中執行block。避免對硬碟的讀寫操作會阻塞到主執行緒介面UI的更新

  3. 刪除檔案的時候,使用NSDirectoryEnumerator來獲取檔案的是否為資料夾的標誌以及修改日期,大小的資訊:

NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

      // This enumerator prefetches useful properties for our cache files.
      NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                 includingPropertiesForKeys:resourceKeys
                                                                    options:NSDirectoryEnumerationSkipsHiddenFiles
                                                               errorHandler:NULL];
複製程式碼

然後根據修改日期和需要刪除的圖片的大小來進行舊檔案的刪除。


Utils

SDWebImageManager

  1. 在載入圖片的過程中,SDWebImage提供幾個相關的選項:
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
  /**
   * 預設情況下,當圖片URL失效或者下載失敗時,該URL會被新增到黑名單中並不會在嘗試載入。
   * 此標誌可以禁止URL被新增到黑名單中
   */
  SDWebImageRetryFailed = 1 << 0,

  /**
   * 預設情況,圖片的載入會發生在UI的互動期間。此標誌位的話會將圖片的載入延遲到UIScrollView滾動結束的期間
   */
  SDWebImageLowPriority = 1 << 1,

  /**
   * 只將圖片快取到記憶體中
   */
  SDWebImageCacheMemoryOnly = 1 << 2,

  /**
   * 開啟進度下載,當有資料了將載入完成的那部分顯示出來,預設情況的話,只會在圖片完全載入完畢之後才將圖片顯示出來
   */
  SDWebImageProgressiveDownload = 1 << 3,

  /**
   * 不管圖片是否已經快取,忽略HTTP響應的快取控制並且重新整理圖片
   * 這個標誌位的話可以用於同一個URL地址的圖片,有的時候,雖然URL相同,但是圖片如果有更新的話,快取機制並不知曉這點。所以需要設定這個標誌位來更新圖片
   */
  SDWebImageRefreshCached = 1 << 4,

  /**
   * 在iOS 4+, 允許app在進入後臺後,繼續下載圖片。這會在app進入後臺時,延長app的駐留時間,在這段時間中來完成圖片的載入
   */
  SDWebImageContinueInBackground = 1 << 5,

  /**
   * 通過將NSMutableURLRequest.HTTPShouldHandleCookies設定為YES來處理NSHTTPCookieStore中的cookie
   */
  SDWebImageHandleCookies = 1 << 6,

  /**
   * 允許使用不被信任的證書
   */
  SDWebImageAllowInvalidSSLCertificates = 1 << 7,

  /**
   * 預設情況下,圖片會根據佇列中的順序進行載入,這個標誌位的話會將圖片的優先順序提高,也就是將圖片的前移
   */
  SDWebImageHighPriority = 1 << 8,
  
  /**
   * 通常情況下,佔點陣圖片會在圖片載入的過程中顯示,這個標誌位的話會將佔位符延遲到圖片載入完成後再進行佔位符的載入
   */
  SDWebImageDelayPlaceholder = 1 << 9,

  /**
   * 使用這個標誌位來進行圖片的變換
   */
  SDWebImageTransformAnimatedImage = 1 << 10,
  
  /**
   * 圖片通常會在載入完成後顯示到imageView上。但是有時候,可以通過這個標誌位來對圖片進行修改之後再進行顯示
   */
  SDWebImageAvoidAutoSetImage = 1 << 11,
  
  /**
   * 預設情況,圖片會根據原始的大小進行解碼操作。在iOS中,會根據裝置的記憶體限制進行縮小。
   */
  SDWebImageScaleDownLargeImages = 1 << 12
};
複製程式碼
  1. 在下載圖片過程中,如果是以下錯誤的話,則不會將圖片URL新增到黑名單當中
  • NSURLErrorNotConnectedToInternet - 無法建立網路連線
  • NSURLErrorCancelled - 任務被取消了
  • NSURLErrorTimedOut - 超時
  • NSURLErrorInternationalRoamingOff - 無法建立漫遊網路
  • NSURLErrorDataNotAllowed - 行動網路不允許建立連線
  • NSURLErrorCannotFindHost - 無法解析主機
  • NSURLErrorCannotConnectToHost - 連線主機時失敗
  • NSURLErrorNetworkConnectionLost - 連線丟失

基本上從上述的錯誤原因能看到,當網路出現故障,無法連線到主機的時候,圖片的URL不會被新增到黑名單當中,SDWebImage會嘗試去重新連線下載

SDWebImageDecoder

  1. 使用CGBitmapContextCreate建立出來的上下文是不含有圖片的透明度資訊的

  2. decodedImageWithImage:方法實際上返回的就是去除了透明通道資訊的圖片

  3. 在解碼圖片時,預設情況下每一個畫素還有4個位元組


Categories

這個模組是其他Cocoa框架的類的Category

NSData+ImageContentType

通過獲取NSData的圖片資料的第一個位元組來判斷圖片的格式

switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
複製程式碼

UIImage+GIF

對於GIF影象的話,獲取第一幀圖片來顯示,而完整的GIF圖片的播放則由FLAnimatedImageView來完成。

UIImage中有一個images陣列屬性,對於GIF這種動態圖片而言, images為非nil。所以根據這個變數是否為nil可以判斷是否為GIF格式

UIImage+MultiFormat

UIImage+WebP

UIView+WebCacheOperation

為UIView新增了一個動態屬性字典,用於儲存圖片的下載操作。


WebCache Categories

這個模組的主要功能是為控制元件新增圖片載入的功能。通過核心方法sd_internalSetImageWithURL:placeholderImage:options:operationKey:setImageBlock:progress:completed:進行圖片載入

相關文章