SDWebImage 圖片下載緩存框架 常用方法及原理


功能:圖片下載、圖片緩存、下載進度監聽、gif處理等等
項目地址:https://github.com/rs/SDWebImage

常見面試題:

  1. SDWebImage的最大並發數是多少?
    _downloadQueue.maxConcurrentOperationCount = 6;
  2. SDWebImages是如何識別圖片的?
    NSData+ImageContentType.m中,根據圖片文件十六進制數據的第一個字節判斷

    圖片的十六進制第一個字節.png
  3. SDWebImage 緩存圖片命名規則?
    為了防止名稱重復,對其進行 md5 運算
  4. 默認下載的超時時長是多少?15秒
  5. 默認緩存的時間?一周
    _maxCacheAge = kDefaultCacheMaxCacheAge;
    static const NSInteger kDefaultCacheMaxCacheAge = 60 60 24 * 7; // 1 week
  6. SDWebImage用什么類型緩存圖片?NSCache
    SDImageCache內處理內存警告,以通知的方式,clearMemory
  7. cleanDisk的執行過程
    i. 先遍歷所有的緩存文件,記錄過期的文件,計算緩存文件的總大小
    ii. 刪除過期的文件
    iii. 判斷maxCacheSize的值是否>0,如果大於0再判斷緩存的文件總大小是否大於maxCacheSize
    iv.如果緩存文件的總大小超過maxCacheSize,刪除最早的文件
    注意:.jpg、.gif等文件需要把擴展名填上,png不需要

一、下載緩存

#import "UIImageView+WebCache.h"'
介紹:使用SDWebImage可以去加載遠程圖片,而且還會緩存圖片,下次請求會看一下是否已經存在於緩存中,如果是的話直接取本地緩存,如果不是的話則重新請求。

1、獲取當前圖片的地址

- (NSURL *)sd_imageURL;

2、下載網絡圖片並緩存

  1. - (void)sd_setImageWithURL:(NSURL *)url;
  2. - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
  3. - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
  4. 下載圖片的線程執行完后回調
    - (void)sd_setImageWithURL:(NSURL *)url completed: (SDWebImageCompletionBlock)completedBlock;
  5. - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
  6. - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
  7. 下載圖片並獲取圖片下載進度 progressBlock
    - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

    例子:下載圖片並且打印圖片的下載進度
    NSURL *url = [NSURL URLWithString:@"http://picview01.baomihua.com/photos/20120624/m_14_634761470842343750_15728444.jpg"]; [self.imageView sd_setImageWithURL:url placeholderImage:nil options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { //乘1.0是為了轉換成float類型 float progress = receivedSize * 1.0 / expectedSize; NSLog(@"下載進度 %f",progress); } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { NSLog(@"完成"); }];

  8. 先從本地緩存中查找請求的圖片,如果有先用本地圖片占位,再從服務器請求下載圖片
    - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

  9. 下載一組動畫圖片,並自動播放動畫(arrayOfURLs為一組圖片的地址數組)
    - (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
  10. 取消當前下載
    - (void)sd_cancelCurrentImageLoad;
  11. 取消下載一組動畫圖片
    - (void)sd_cancelCurrentAnimationImagesLoad;

  12. 設置是否顯示活的指示器以及樣式
    注意:必須在請求下載圖片之前給UIImageView設置!不然無法顯示!
    - (void)setShowActivityIndicatorView:(BOOL)show;
    - (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style;


二、 UIButton的類擴展

#import "UIButton+WebCache.h"

  1. 獲取當前按鈕圖片的地址
    - (NSURL *)sd_currentImageURL;
  2. 獲取指定狀態下按鈕圖片地址
    - (NSURL *)sd_imageURLForState:(UIControlState)state;
  3. 設置不同UIControlState狀態下的按鈕圖片
    - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state;
    - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
    - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
    - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
    - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
    - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;

  4. 設置不同狀態的按鈕背景圖片
    - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state;
    - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
    - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
    - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
    - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
    - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;

  5. 取消當前指定狀態按鈕圖片下載
    - (void)sd_cancelImageLoadForState:(UIControlState)state;

  6. 取消當前指定狀態按鈕背景圖片下載
    - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state;

三、參數介紹

options 枚舉:
  1. SDWebImageRetryFailed 失敗后重試, 默認如果下載失敗,這個url會被加入黑名單並且不會嘗試再次下載,設置這個枚舉會阻止把失敗的url加入黑名單,不斷嘗試重新下載
  2. SDWebImageLowPriority 延遲下載, 默認情況下,圖片會在交互發生的時候下載(例如你滑動tableview的時候),這個枚舉會禁止這個特性,導致的結果就是在scrollview減速的時候才會開始下載(也就是你滑動的時候scrollview不下載,你手從屏幕上移走,scrollview開始減速的時候才會開始下載圖片)
  3. SDWebImageCacheMemoryOnly 只在內存緩存
  4. SDWebImageProgressiveDownload 漸進式下載,顯示的圖像是逐步在下載
  5. SDWebImageRefreshCached 刷新緩存,有時本地圖片更新后與服務器沒有同步一致時可以使用(例如更新頭像),專門處理相同url,但不同image的情況的
    原因:默認情況下,SDWebImage會忽略Header中的緩存設置,將圖片以url為key進行保存,url與圖片是一一對應關系。所以請求同一個url時,SDWebImage會從緩存中取得圖片。一般的情況下用此方法可以滿足我們的應用要求,但是如果你請求同一個url,而這張圖片在服務器端更新了,本地客戶端再次請求時還是會返回緩存中的舊圖片,例如加載頭像類經常更新的圖片時,就會出現頭像不能更新的問題,由於url與圖片一一對應,一種解決的辦法是改變部分url地址方式實現更新,不過這種方法操作起來很復雜, 另一種將第三個參數設置為SDWebImageRefreshCached就可以實現圖片更新操作了。
  6. SDWebImageContinueInBackground 啟動后台下載,app進入后台后繼續下載
  7. SDWebImageHandleCookies 處理存儲在NSHTTPCookieStore中的cookie
    NSMutableURLRequest.HTTPShouldHandleCookies = YES;
  8. SDWebImageAllowInvalidSSLCertificates 允許使用無效的SSL證書,主要用於測試目的,在正式環境中慎用
  9. SDWebImageHighPriority 優先下載
  10. SDWebImageDelayPlaceholder 等待下載完成后再顯示占位圖片,延遲顯示占位圖片
  11. SDWebImageTransformAnimatedImage 改變動畫形象
  12. SDWebImageAvoidAutoSetImage 下載完成后手動設置圖片,默認是下載完成后自動放到ImageView上
SDWebImageCompletionBlock

typedef void(^SDWebImageCompletionBlock)(UIImage image, NSError error, SDImageCacheType cacheType, NSURL *imageURL);
參數:
(1)請求的圖片
(2)請求圖片為空的錯誤
(3) SDImageCacheType 緩存類型,下次是從網上獲取還是從本地獲取, 枚舉:
SDImageCacheTypeNone 永不緩存,但是從網上下載
SDImageCacheTypeDisk 只緩存到磁盤上
SDImageCacheTypeMemory 只緩存到內存中
(4)圖片的網絡地址

SDWebImageDownloaderProgressBlock

typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
progress 參數:
receivedSize 接收到的字節數
expectedSize 期望下載的字節數
//乘1.0是為了轉換成float類型
float progress = receivedSize * 1.0 / expectedSize;


四、本地緩存

#import"SDImageCache.h"
介紹:很多時候我們可能拍照得到的一張圖片要多個地方使用,那么我們就希望可以把這張圖片放到緩存里面,然后每次用這張圖片的時候就去通過特定的方式取即可。

屬性:
  1. 是否壓縮圖片
    @property (assign, nonatomic) BOOL shouldDecompressImages;
  2. 是否禁用iCloud,默認YES
    @property (assign, nonatomic) BOOL shouldDisableiCloud;
  3. 是否緩存到內存,默認YES
    @property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
  4. 最大緩存成本,針對內存
    @property (assign, nonatomic) NSUInteger maxMemoryCost;
  5. 最大緩存個數,針對內存
    @property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
  6. 最大緩存時間,單位:秒,針對磁盤緩存,默認是一周,自動把一星期以前緩存的圖片刪除掉
    @property (assign, nonatomic) NSInteger maxCacheAge;
  7. 最大緩存大小,單位字節,針對磁盤,默認無限制0,需要自己設置
    @property (assign, nonatomic) NSUInteger maxCacheSize;
方法:
1、獲取緩存單例對象

+ (SDImageCache *)sharedImageCache;

2、創建緩存空間
  1. 在沙盒的cache目錄下創建一個指定名字的緩存空間(文件夾)
    - (id)initWithNamespace:(NSString *)ns;
  2. 在磁盤指定目錄(directory)下創建一個指定名字的緩存空間
    - (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
  3. 添加一個只讀的緩存路徑
    - (void)addReadOnlyCachePath:(NSString *)path;
3、緩存圖片到內存和磁盤上
  1. 往內存和磁盤上存儲一個圖片(key參數是唯一的,用來取出圖片,一般是圖片的絕對路徑)
    - (void)storeImage:(UIImage *)image forKey:(NSString *)key;
    例子:
    SDImageCache *imageCache = [SDImageCache sharedImageCache]; [imageCache storeImage:image forKey:@"myphoto" toDisk:YES];
  2. 緩存一個圖片到內存,並設置是否緩存到磁盤上
    - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
  3. 緩存的基礎方法,緩存一個圖片到內存,並設置是否緩存到磁盤上(recalculate 是否重新計算圖片的data , imageData 圖片的data)
    - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
4、異步在磁盤上查找指定key圖片的緩存,完成后回調block

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

5、從內存、磁盤獲取指定key的圖片

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;

6、刪除指定key的圖片(fromDisk 是否刪除磁盤緩存,completion刪除結束后回調無參block)

- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;

7、清除所有緩存
  1. 清除所有內存圖片緩存
    - (void)clearMemory;
  2. 清除所有磁盤緩存圖片時回調一個block
    - (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
  3. 清除所有磁盤緩存
    - (void)clearDisk;
  4. 從磁盤刪除所有過期的圖片時立即調用
    - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
  5. 刪除所有磁盤上過期的緩存圖片
    - (void)cleanDisk;
8、獲取緩存大小、緩存個數
  1. 獲取磁盤緩存大小
    - (NSUInteger)getSize;
  2. 獲取磁盤上緩存圖片的個數
    - (NSUInteger)getDiskCount;
  3. 異步計算磁盤緩存的大小
    - (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
9、從磁盤查找緩存圖片
  1. 異步從磁盤中查找指定key的圖片緩存,查找完成后回調這個block(該block永遠在主線程執行)
    - (void)diskImageExistsWithKey:(NSString*)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
  2. 檢查磁盤中是否存在指定key的圖片緩存
    - (BOOL)diskImageExistsWithKey:(NSString *)key;
  3. 通過圖片的key獲取其緩存路徑(path:緩存路徑的根路徑)
    - (NSString*)cachePathForKey:(NSString*)key inPath:(NSString*)path;
  4. 從默認緩存路徑下獲取指定key圖片的路徑
    - (NSString *)defaultCachePathForKey:(NSString *)key;

五、播放gif

#import "UIImage+GIF.h"

  1. 播放指定名字gif圖片
    + (UIImage *)sd_animatedGIFNamed:(NSString *)name;
  2. 用指定gif的data播放
    + (UIImage *)sd_animatedGIFWithData:(NSData *)data;
  3. 設置gif圖片尺寸
    - (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;

六、SDWebImage 實現原理


SDWebImage框架結構.png

先講下大體架構:最外層是一個SDWebImageManager單例工具類管理另外兩個單例類,一個是作下載處理的SDWebImageDownloader,它管理多個下載操作SDWebImageDownloaderOperation,一個作緩存處理的SDImageCache。

大體執行過程:當我們需要下載圖片時,先讓SDImageCache從緩存中找,如果找不到就異步從硬盤中讀取圖片,如果讀取到就將圖片緩存到內存並回調給SDWebImageManager,如果找不到就讓SDWebImageDownloader 執行下載操作,在 SDWebImageDownloaderOperation 單個圖片的下載操作中利用 NSURLConnection 執行下載,實現代理監聽下載進度等,下載完成后交給SDWebImageDecoder 圖片異步解碼,完成后回調給SDWebImageDownloader,再回調給SDWebImageManager,再讓SDImageCache去執行內存和磁盤(異步)的緩存操作。

具體過程:

  • UIImageView+WebCache: setImageWithURL:placeholderImage:options: 先顯示 placeholderImage ,同時由SDWebImageManager 根據 URL 來在本地查找圖片。
  • SDWebImageManager: downloadWithURL:delegate:options:userInfo: SDWebImageManager是將UIImageView+WebCache同SDImageCache鏈接起來的類, SDImageCache: queryDiskCacheForKey:delegate:userInfo:用來從緩存根據CacheKey查找圖片是否已經在緩存中
  • 如果內存中已經有圖片緩存, SDWebImageManager會回調SDImageCacheDelegate : imageCache:didFindImage:forKey:userInfo:
  • 而 UIImageView+WebCache 則回調SDWebImageManagerDelegate: webImageManager:didFinishWithImage:來顯示圖片。
  • 如果內存中沒有圖片緩存,那么生成 NSInvocationOperation 添加到隊列,從硬盤查找圖片是否已被下載緩存。
  • 根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結果回調
    notifyDelegate:
  • 如果上一操作從硬盤讀取到了圖片,將圖片添加到內存緩存中(如果空閑內存過小,會先清空內存緩存)。SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo:進而回調展示圖片。
  • 如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調
    imageCache:didNotFindImageForKey:userInfo:
  • 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
  • 圖片下載由 NSURLConnection 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
  • connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
  • connectionDidFinishLoading: 數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
  • 圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
  • 在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調給 SDWebImageDownloader。
  • imageDownloader:didFinishWithImage: 回調給 SDWebImageManager 告知圖片下載完成。
  • 通知所有的 downloadDelegates 下載完成,回調給需要的地方展示圖片。
  • 將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。
  • 寫文件到硬盤在單獨 NSInvocationOperation 中完成,避免拖慢主線程。
  • 如果是在iOS上運行,SDImageCache 在初始化的時候會注冊notification 到 UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在內存警告的時候清理內存圖片緩存,應用結束的時候清理過期圖片。
  • SDWebImagePrefetcher 可以預先下載圖片,方便后續使用。



文/滕先洪(簡書作者)
原文鏈接:http://www.jianshu.com/p/4191017c8b39
著作權歸作者所有,轉載請聯系作者獲得授權,並標注“簡書作者”。



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM