SDWebImage實(shí)現(xiàn)原理

功能:
1.提供了一個UIImageView的category用來加載網(wǎng)絡(luò)圖片并且對網(wǎng)絡(luò)圖片的緩存進(jìn)行管理
2.采用異步方式來下載網(wǎng)絡(luò)圖片
3.采用異步方式,使用memory+disk來緩存網(wǎng)絡(luò)圖片,自動管理緩存。
4.支持GIF動畫
5.支持WebP格式(是一種可以快速加載圖片的格式,圖片壓縮體積大約只有JPEG的2/3)
6.同一個URL的網(wǎng)絡(luò)圖片不會被重復(fù)下載
7.失效的URL不會被無限重試
8.耗時(shí)操作都在子線程,確保不會阻塞主線程
9.使用GCD和ARC
10.支持Arm64
概括的說就是一下:
圖片下載、圖片緩存、下載進(jìn)度監(jiān)聽、gif處理,WebP格式圖片下載等等
demo項(xiàng)目地址:https://git.oschina.net/leiming97/SDWebimage_xiaowang
SDWebImage框架地址:https://github.com/rs/SDWebImage

常見面試題:

SDWebImage的最大并發(fā)數(shù)是多少?
_downloadQueue.maxConcurrentOperationCount = 6;
SDWebImages是如何識別圖片的?
NSData+ImageContentType.m中,根據(jù)圖片文件十六進(jìn)制數(shù)據(jù)的第一個字節(jié)判斷
圖片的十六進(jìn)制第一個字節(jié).png
switch(c){
         case 0xFF:
             return @"image/jpeg";
         case 0x89:
             return @"image/png";
         case 0x47:
             return @"image/gif";
         case 0x49:
         case 0x4D:
             return @"image/tiff";
         case 0X52:
         if([date Length] < 12)
           return nil;
}

SDWebImage 緩存圖片命名規(guī)則?
為了防止名稱重復(fù),對其進(jìn)行 md5 運(yùn)算
默認(rèn)下載的超時(shí)時(shí)長是多少?15秒
默認(rèn)緩存的時(shí)間?一周
_maxCacheAge = kDefaultCacheMaxCacheAge;
static const NSInteger kDefaultCacheMaxCacheAge = 60 60 24 * 7; // 1 week
SDWebImage用什么類型緩存圖片?NSCache

 接受到系統(tǒng)內(nèi)存警告時(shí)如何處理(面試)
(1) 取消當(dāng)前正在進(jìn)行的所有下載操作
[[SDWebImageManager sharedManager] cancelAll];
(2)清楚緩存數(shù)據(jù)
<1>
[[SDWebImageManager sharedManager].imageCache cleanDisk];
cleanDisk:   刪除過期的文件數(shù)據(jù),計(jì)算當(dāng)前未過期的已經(jīng)下載的文件數(shù)據(jù)的大小,發(fā)現(xiàn)該數(shù)據(jù)大小大于我們設(shè)置的最大緩存數(shù)據(jù)大小,那么程序內(nèi)部就會按照文件數(shù)據(jù)緩存的時(shí)間從遠(yuǎn)到近刪除,直到小于最大緩存數(shù)據(jù)為止.
<2> 
[[SDWebImageManager sharedManager].imageCache clearMemory];
clearMemory:直接刪除文件,重新創(chuàng)建新的文件夾

注意:.jpg、.gif等文件需要把擴(kuò)展名填上,png不需要

一、下載緩存

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

1、獲取當(dāng)前圖片的地址
- (NSURL *)sd_imageURL;

2、下載網(wǎng)絡(luò)圖片并緩存
- (void)sd_setImageWithURL:(NSURL *)url;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
下載圖片的線程執(zhí)行完后回調(diào)
- (void)sd_setImageWithURL:(NSURL *)url completed: (SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
下載圖片并獲取圖片下載進(jìn)度 progressBlock
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

例子:下載圖片并且打印圖片的下載進(jìn)度

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是為了轉(zhuǎn)換成float類型
float progress = receivedSize * 1.0 / expectedSize;
NSLog(@"下載進(jìn)度 %f",progress);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"完成");
}];

先從本地緩存中查找請求的圖片,如果有先用本地圖片占位,再從服務(wù)器請求下載圖片

- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

下載一組動畫圖片,并自動播放動畫(arrayOfURLs為一組圖片的地址數(shù)組)
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
取消當(dāng)前下載
- (void)sd_cancelCurrentImageLoad;
取消下載一組動畫圖片
- (void)sd_cancelCurrentAnimationImagesLoad;

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

二、 UIButton的類擴(kuò)展

#import "UIButton+WebCache.h"

獲取當(dāng)前按鈕圖片的地址
- (NSURL *)sd_currentImageURL;
獲取指定狀態(tài)下按鈕圖片地址
- (NSURL *)sd_imageURLForState:(UIControlState)state;
設(shè)置不同UIControlState狀態(tài)下的按鈕圖片
- (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;

設(shè)置不同狀態(tài)的按鈕背景圖片
- (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;

取消當(dāng)前指定狀態(tài)按鈕圖片下載
- (void)sd_cancelImageLoadForState:(UIControlState)state;

取消當(dāng)前指定狀態(tài)按鈕背景圖片下載
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state;

三、參數(shù)介紹

options 枚舉:
SDWebImageRetryFailed 失敗后重試, 默認(rèn)如果下載失敗,這個url會被加入黑名單并且不會嘗試再次下載,設(shè)置這個枚舉會阻止把失敗的url加入黑名單,不斷嘗試重新下載
SDWebImageLowPriority 延遲下載, 默認(rèn)情況下,圖片會在交互發(fā)生的時(shí)候下載(例如你滑動tableview的時(shí)候),這個枚舉會禁止這個特性,導(dǎo)致的結(jié)果就是在scrollview減速的時(shí)候才會開始下載(也就是你滑動的時(shí)候scrollview不下載,你手從屏幕上移走,scrollview開始減速的時(shí)候才會開始下載圖片)
SDWebImageCacheMemoryOnly 只在內(nèi)存緩存
SDWebImageProgressiveDownload 漸進(jìn)式下載,顯示的圖像是逐步在下載
SDWebImageRefreshCached 刷新緩存,有時(shí)本地圖片更新后與服務(wù)器沒有同步一致時(shí)可以使用(例如更新頭像),專門處理相同url,但不同image的情況的
原因:默認(rèn)情況下,SDWebImage會忽略Header中的緩存設(shè)置,將圖片以url為key進(jìn)行保存,url與圖片是一一對應(yīng)關(guān)系。所以請求同一個url時(shí),SDWebImage會從緩存中取得圖片。一般的情況下用此方法可以滿足我們的應(yīng)用要求,但是如果你請求同一個url,而這張圖片在服務(wù)器端更新了,本地客戶端再次請求時(shí)還是會返回緩存中的舊圖片,例如加載頭像類經(jīng)常更新的圖片時(shí),就會出現(xiàn)頭像不能更新的問題,由于url與圖片一一對應(yīng),一種解決的辦法是改變部分url地址方式實(shí)現(xiàn)更新,不過這種方法操作起來很復(fù)雜, 另一種將第三個參數(shù)設(shè)置為SDWebImageRefreshCached就可以實(shí)現(xiàn)圖片更新操作了。
SDWebImageContinueInBackground 啟動后臺下載,app進(jìn)入后臺后繼續(xù)下載
SDWebImageHandleCookies 處理存儲在NSHTTPCookieStore中的cookie
NSMutableURLRequest.HTTPShouldHandleCookies = YES;
SDWebImageAllowInvalidSSLCertificates 允許使用無效的SSL證書,主要用于測試目的,在正式環(huán)境中慎用
SDWebImageHighPriority 優(yōu)先下載
SDWebImageDelayPlaceholder 等待下載完成后再顯示占位圖片,延遲顯示占位圖片
SDWebImageTransformAnimatedImage 改變動畫形象
SDWebImageAvoidAutoSetImage 下載完成后手動設(shè)置圖片,默認(rèn)是下載完成后自動放到ImageView上
SDWebImageCompletionBlock
typedef void(^SDWebImageCompletionBlock)(UIImage image, NSError error, SDImageCacheType cacheType, NSURL *imageURL);
參數(shù):
(1)請求的圖片
(2)請求圖片為空的錯誤
(3) SDImageCacheType 緩存類型,下次是從網(wǎng)上獲取還是從本地獲取, 枚舉:
SDImageCacheTypeNone 永不緩存,但是從網(wǎng)上下載
SDImageCacheTypeDisk 只緩存到磁盤上
SDImageCacheTypeMemory 只緩存到內(nèi)存中
(4)圖片的網(wǎng)絡(luò)地址

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

四、本地緩存

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

屬性:
是否壓縮圖片
@property (assign, nonatomic) BOOL shouldDecompressImages;
是否禁用iCloud,默認(rèn)YES
@property (assign, nonatomic) BOOL shouldDisableiCloud;
是否緩存到內(nèi)存,默認(rèn)YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
最大緩存成本,針對內(nèi)存
@property (assign, nonatomic) NSUInteger maxMemoryCost;
最大緩存?zhèn)€數(shù),針對內(nèi)存
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
最大緩存時(shí)間,單位:秒,針對磁盤緩存,默認(rèn)是一周,自動把一星期以前緩存的圖片刪除掉
@property (assign, nonatomic) NSInteger maxCacheAge;
最大緩存大小,單位字節(jié),針對磁盤,默認(rèn)無限制0,需要自己設(shè)置
@property (assign, nonatomic) NSUInteger maxCacheSize;
方法:
1、獲取緩存單例對象
+ (SDImageCache *)sharedImageCache;

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

5、從內(nèi)存、磁盤獲取指定key的圖片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;

6、刪除指定key的圖片(fromDisk 是否刪除磁盤緩存,completion刪除結(jié)束后回調(diào)無參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、清除所有緩存
清除所有內(nèi)存圖片緩存
- (void)clearMemory;
清除所有磁盤緩存圖片時(shí)回調(diào)一個block
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
清除所有磁盤緩存
- (void)clearDisk;
從磁盤刪除所有過期的圖片時(shí)立即調(diào)用
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
刪除所有磁盤上過期的緩存圖片
- (void)cleanDisk;
8、獲取緩存大小、緩存?zhèn)€數(shù)
獲取磁盤緩存大小
- (NSUInteger)getSize;
獲取磁盤上緩存圖片的個數(shù)
- (NSUInteger)getDiskCount;
異步計(jì)算磁盤緩存的大小
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
9、從磁盤查找緩存圖片
異步從磁盤中查找指定key的圖片緩存,查找完成后回調(diào)這個block(該block永遠(yuǎn)在主線程執(zhí)行)
- (void)diskImageExistsWithKey:(NSString*)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
檢查磁盤中是否存在指定key的圖片緩存
- (BOOL)diskImageExistsWithKey:(NSString *)key;
通過圖片的key獲取其緩存路徑(path:緩存路徑的根路徑)
- (NSString*)cachePathForKey:(NSString*)key inPath:(NSString*)path;
從默認(rèn)緩存路徑下獲取指定key圖片的路徑
- (NSString *)defaultCachePathForKey:(NSString *)key;

五、播放gif

#import "UIImage+GIF.h"

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

六、SDWebImage 實(shí)現(xiàn)原理

1385290-a75ee

先講下大體架構(gòu):
最外層是一個SDWebImageManager單例工具類管理另外兩個單例類,一個是作下載處理的SDWebImageDownloader,它管理多個下載操作SDWebImageDownloaderOperation,一個作緩存處理的SDImageCache。
大體執(zhí)行過程:當(dāng)我們需要下載圖片時(shí),先讓SDImageCache從緩存中找,如果找不到就異步從硬盤中讀取圖片,如果讀取到就將圖片緩存到內(nèi)存并回調(diào)給SDWebImageManager,如果找不到就讓SDWebImageDownloader 執(zhí)行下載操作,在 SDWebImageDownloaderOperation 單個圖片的下載操作中利用 NSURLConnection 執(zhí)行下載,實(shí)現(xiàn)代理監(jiān)聽下載進(jìn)度等,下載完成后交給SDWebImageDecoder 圖片異步解碼,完成后回調(diào)給SDWebImageDownloader,再回調(diào)給SDWebImageManager,再讓SDImageCache去執(zhí)行內(nèi)存和磁盤(異步)的緩存操作。

具體過程:

具體加載圖片.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容