iOS SDWebImage 學習

orilme發表於2019-04-06

官方SDWebImage的架構圖

官方SDWebImage的架構圖

SDWebImage庫的作用:

通過對UIImageView的類別擴充套件來實現非同步載入替換圖片的工作。
主要用到的物件:

  1. UIImageView (WebCache)類別,入口封裝,實現讀取圖片完成後的回撥
  2. SDWebImageManager,對圖片進行管理的中轉站,記錄哪些圖片正在讀取
    (1)向下層讀取Cache(呼叫SDImageCache),或者向網路讀取物件(呼叫SDWebImageDownloader)。 (2)實現SDImageCache和SDWebImageDownloader的回撥
  3. SDImageCache
    (1)根據URL的MD5摘要對圖片進行儲存和讀取(實現存在記憶體中或者存在硬碟上兩種實現)
    (2)實現圖片和記憶體清理工作
  4. SDWebImageDownloader,根據URL向網路讀取資料(實現部分讀取和全部讀取後再通知回撥兩種方式)

SDWebImage 快取流程

官方SDWebImage的流程圖

以最為常用的UIImageView為例:

  1. UIImageView+WebCache:  setImageWithURL:placeholderImage:options: 先顯示 placeholderImage ,同時由SDWebImageManager 根據 URL 來在本地查詢圖片。
  2. SDWebImageManager: downloadWithURL:delegate:options:userInfo: SDWebImageManager是將UIImageView+WebCache同SDImageCache連結起來的類, SDImageCache: queryDiskCacheForKey:delegate:userInfo:用來根據CacheKey查詢圖片是否已經在快取中
  3. 如果記憶體中已經有圖片快取, SDWebImageManager會回撥SDImageCacheDelegate : imageCache:didFindImage:forKey:userInfo:
  4. 而 UIImageView+WebCache 則回撥SDWebImageManagerDelegate:  webImageManager:didFinishWithImage:來顯示圖片。
  5. 如果記憶體中沒有圖片快取,那麼生成 NSInvocationOperation 新增到佇列,從硬碟查詢圖片是否已被下載快取。
  6. 根據 URLKey 在硬碟快取目錄下嘗試讀取圖片檔案。這一步是在 NSOperation 進行的操作,所以回主執行緒進行結果回撥 notifyDelegate:。
  7. 如果上一操作從硬碟讀取到了圖片,將圖片新增到記憶體快取中(如果空閒記憶體過小,會先清空記憶體快取)。SDImageCacheDelegate 回撥 imageCache:didFindImage:forKey:userInfo:。進而回撥展示圖片。
  8. 如果從硬碟快取目錄讀取不到圖片,說明所有快取都不存在該圖片,需要下載圖片,回撥 imageCache:didNotFindImageForKey:userInfo:。
  9. 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
  10. 圖片下載由 NSURLSession 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
  11. connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度載入效果。
  12. connectionDidFinishLoading: 資料下載完成後交給 SDWebImageDecoder 做圖片解碼處理。
  13. 圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主執行緒 UI。如果有需要對下載的圖片進行二次處理,最好也在這裡完成,效率會好很多。
  14. 在主執行緒 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回撥給 SDWebImageDownloader。
  15. imageDownloader:didFinishWithImage: 回撥給 SDWebImageManager 告知圖片下載完成。
  16. 通知所有的 downloadDelegates 下載完成,回撥給需要的地方展示圖片。
  17. 將圖片儲存到 SDImageCache 中,記憶體快取和硬碟快取同時儲存。
  18. 寫檔案到硬碟在單獨 NSInvocationOperation 中完成,避免拖慢主執行緒。
  19. 如果是在iOS上執行,SDImageCache 在初始化的時候會註冊notification 到UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在記憶體警告的時候清理記憶體圖片快取,應用結束的時候清理過期圖片。
  20. SDWebImagePrefetcher 可以預先下載圖片,方便後續使用。

 

SDWebImage 使用

  • 檢視快取大小

    - (NSString *)readSDWebImageCache {
        NSUInteger size = [SDImageCache sharedImageCache].getSize;
        // 1k = 1024, 1m = 1024k
        if (size < 1024) { // 小於1k
            return [NSString stringWithFormat:@"%ldB",(long)size];
        }else if (size < 1024 * 1024) { // 小於1m
            CGFloat aFloat = size/1024;
            return [NSString stringWithFormat:@"%.0fK",aFloat];
        }else if (size < 1024 * 1024 * 1024) { // 小於1G
            CGFloat aFloat = size/(1024 * 1024);
            return [NSString stringWithFormat:@"%.1fM",aFloat];
        }else {
            CGFloat aFloat = size/(1024*1024*1024);
            return [NSString stringWithFormat:@"%.1fG",aFloat];
        }
    }
    複製程式碼
  • 清除快取

    - (void)clearDisk {
        NSLog(@"SDWebImageCache---%@", [self readSDWebImageCache]);
        [[SDImageCache sharedImageCache] clearDiskOnCompletion:nil];
        [[SDImageCache sharedImageCache] clearMemory]; //可不寫
        NSLog(@"SDWebImageCache2---%@", [self readSDWebImageCache]);
    }
    複製程式碼

 

SDWebImage 快取

  • 清理快取圖片的策略:

    特別是最大快取空間大小的設定。如果所有快取檔案的總大小超過這一大小,則會按照檔案最後修改時間的逆序,以每次一半的遞迴來移除那些過早的檔案,直到快取的實際大小小於我們設定的最大使用空間。
    注意:它預設只支援超過7天的圖片清除。不對圖片快取大小進行控制。當然它已經做了這種機制,只是maxCacheSize為預設值0,所以不生效。
    1. 遍歷快取目錄使用下面函式
    NSArray *resourceKeys = @[NSURLIsDirectoryKey,   NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
    
    NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                includingPropertiesForKeys:resourceKeys                                                              options:NSDirectoryEnumerationSkipsHiddenFiles
                                                              errorHandler:NULL];
    複製程式碼
    1. 歸檔過期快取
     for (NSURL *fileURL in fileEnumerator) {
         ......
         // 根據檔案路徑最後修改時間來獲取內容
         NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
     // 判斷是否過快取期
         if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate])         {
             [urlsToDelete addObject:fileURL];
             continue;
         }
    
         // 這裡同時對未過期的檔案根據檔案大小進行歸檔,便以後續重置快取.
         NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
         currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
         [cacheFiles setObject:resourceValues forKey:fileURL];
     }
    複製程式碼
    1. 刪除過期快取
     for (NSURL *fileURL in urlsToDelete) {
         [_fileManager removeItemAtURL:fileURL error:nil];
     } 
    複製程式碼
    1. 重置快取大小
    // 依據檔案修改時間,對未過期的檔案進行升序排序.
     NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                     }];
     
     // 根據設定的快取大小,對當前快取進行調整,刪除那些快過期的檔案,使當前總的檔案大小小與設定的快取大小。
     for (NSURL *fileURL in sortedFiles) {
         if ([_fileManager removeItemAtURL:fileURL error:nil]) {
             NSDictionary *resourceValues = cacheFiles[fileURL];
             NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
             currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
             
             if (currentCacheSize < desiredCacheSize) {
                 break;
             }
         }
     }
    複製程式碼
  • app事件註冊使用經典的觀察者模式,當觀察到記憶體警告、程式被終止、程式進入後臺這些事件時,程式將自動呼叫相應的方法處理

    當收到系統記憶體告警通知時,對記憶體快取進行處理
    [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(clearMemory)
                                                   name:UIApplicationDidReceiveMemoryWarningNotification
                                                 object:nil];
                                                 
                                                 
    複製程式碼
    當程式終止時,對快取檔案進行處理
    [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(cleanDisk)
                                                   name:UIApplicationWillTerminateNotification
                                                 object:nil];
    複製程式碼
    當進入後臺執行時,對快取檔案進行處理
    [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(backgroundCleanDisk)
                                                   name:UIApplicationDidEnterBackgroundNotification
                                                 object:nil];
    複製程式碼

 

SDWebImage 原始碼解析

iOS SDWebImage 學習
SDImageDownloader負責管理所有的下載任務,具體的下載任務由SDImageDownloaderOperation類負責。

  • SDWebImageDownloaderOperation
    SDWebImageDownloaderOperation原始碼解析
    開發者就可以不使用SDWebImage提供的下載任務類,而可以自定義相關類,只需要遵守協議即可,SDWebImageDownloaderOperation類也遵守了該協議,該類繼承自 NSOperation 主要是為了將任務加進併發佇列裡實現多執行緒下載多張圖片,真正實現下載操作的是 NSURLSessionTask 類的子類,這裡就可以看出 SDWebImage 使用 NSURLSession 實現下載圖片的功能
  • SDImageDownloader
    SDImageDownloader原始碼解析
    SDWebImage主要使用了自定義NSOperation子類,並在這個自定義NSOperation子類中通過一個可用的NSURLSession來建立一個執行伺服器互動資料的NSURLSessionDataTask的下載任務,並由其全權負責下載工作,接著使用NSOperationQueue實現多執行緒的多圖片下載。

 

其他

  • 常見SDWebImageOptions
    SDWebImageRetryFailed, 下載失敗後會自動重新下載
    SDWebImageLowPriority, 當正在與UI進行互動時,自動暫停內部的一些下載功能
    SDWebImageRetryFailed | SDWebImageLowPriority,同時存在上邊兩種
    SDWebImageCacheMemoryOnly, 取消磁碟快取只有記憶體快取
    SDWebImageProgressiveDownload,預設情況,影像會在下載完成後一次性顯示
  • 預設儲存法都是是記憶體快取和磁碟快取結合的方式。如果你只需要記憶體快取,那麼在帶options選項的方法options這裡選擇SDWebImageCacheMemoryOnly就可以了
  • 對於圖片的快取實際應用的是NSURLCache自帶的cache機制。NSURLCache每次都要把快取的raw data 再轉化為UIImage
  • SDWebImage提供瞭如下三個category來進行快取
    MKAnnotationView(WebCache)
    UIButton(WebCache)
    UIImageView(WebCache)
  • 比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:
    [[SDWebImageManager sharedManager].imageDownloader downloadImageWithURL:urlPath options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
       NSLog(@"下載進度---%f", (float)receivedSize/expectedSize);
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
       NSLog(@"下載完成---%@", [NSThread currentThread]);
    }];
    複製程式碼
  • 圖片下載速度不一致,使用者快速滾動的時候,會因為cell重用導致圖片混亂
    解決辦法:MVC,使用模型保持下載的影像,再次重新整理表格。
  • 將影像儲存到模型裡的優缺點
    優點:不用重複下載,利用MVC重新整理表格,不會造成資料混亂,載入速度比較快
    缺點:記憶體。所有下載好的影像,都會記錄在模型裡。如果資料比較多(2000)造成記憶體警告
  • 圖片格式簡介
    PNG:無失真壓縮,壓縮比較低,PNG圖片一般會比JPG大。(GPU解壓縮的消耗非常小,解壓縮的速度比較快,比較清晰,蘋果推薦使用)
    JPG:有失真壓縮!壓縮比非常高!照相機使用(GPU解壓縮的消耗非常大)
    GIF:動圖
    BMP:點陣圖,沒有任何壓縮,幾乎不用
  • SDWebImage自己的編解碼技術
    在展示一張圖片的時候常使用imageNamed:這樣的類方法去獲取並展示這張圖片,但是圖片是以二進位制的格式儲存在磁碟或記憶體中的,如果要展示一張圖片需要根據圖片的不同格式去解碼為正確的點陣圖交由系統控制元件來展示,而解碼的操作預設是放在主執行緒執行,凡是放在主執行緒執行的任務都務必需要考慮清楚,如果有大量圖片要展示,就會在主執行緒中執行大量的解碼任務,勢必會阻塞主執行緒造成卡頓,所以SDWebImage自己實現相關的編解碼操作,並在子執行緒中處理,就不會影響主執行緒的相關操作

更多實用詳見 Demo VenderExplore資料夾下

相關文章