在iOS的圖片加載框架中,SDWebImage使用頻率非常高。它支持從網(wǎng)絡中下載且緩存圖片,并設置圖片到對應的UIImageView控件或者UIButton控件。在項目中使用SDWebImage來管理圖片加載相關操作可以極大地提高開發(fā)效率,讓我們更加專注于業(yè)務邏輯實現(xiàn)。本文講解的版本為4.4.2版本。
文章目錄結構
一 SDWebImage概論
二 SDWebImage組織架構
三 各個類詳解
一 SDWebImage 概論
SDWebImage是個支持異步下載與緩存的UIImageView擴展。項目主要提供了一下功能:
1.提供了一個UIImageView的category用來加載網(wǎng)絡圖片并且對網(wǎng)絡圖片的緩存進行管理
2.采用異步方式來下載網(wǎng)絡圖片
3.采用異步方式,使用內(nèi)存+磁盤來緩存網(wǎng)絡圖片,擁有自動的緩存過期處理機制。
4.支持GIF動畫
5.支持WebP格式
6.同一個URL的網(wǎng)絡圖片不會被重復下載
7.失效,虛假的URL不會被無限重試
8.耗時操作都在子線程,確保不會阻塞主線程
9.使用GCD和ARC
10.支持Arm64
11.支持后臺圖片解壓縮處理
12.項目支持的圖片格式包括 PNG,JPEG,GIF,Webp等
二 SDWebImage組織架構

關鍵類講解
SDWebImageDownloader:負責維持圖片的下載隊列;
SDWebImageDownloaderOperation:負責真正的圖片下載請求;
SDImageCache:負責圖片的緩存;
SDWebImageManager:是總的管理類,維護了一個SDWebImageDownloader實例和一個SDImageCache實例,是下載與緩存的橋梁;
SDWebImageDecoder:負責圖片的解壓縮;
SDWebImagePrefetcher:負責圖片的預??;
UIImageView+WebCache:和其他的擴展都是與用戶直接打交道的。
其中,最重要的三個類就是SDWebImageDownloader、SDImageCache、SDWebImageManager。接下來我們就分別詳細地研究一下這些類各自具體做了哪些事,又是怎么做的。
為了便于大家從宏觀上有個把握,這里先給出項目的框架結構:

- UIImageView+WebCache和UIButton+WebCache直接為表層的 UIKit框架提供接口
- SDWebImageManger負責處理和協(xié)調(diào)SDWebImageDownloader和SDWebImageCache, 并與 UIKit層進行交互。
- SDWebImageDownloaderOperation真正執(zhí)行下載請求;最底層的兩個類為高層抽象提供支持。
三 各個類詳解
我們按照上圖中從上到下執(zhí)行的流程來研究各個類
3.1 UIImageView+WebCache
這里只用UIImageView+WebCache來舉個例子,其他的擴展類似。
使用場景:已知圖片的url地址,下載圖片并設置到UIImageView上。
UIImageView+WebCache提供了一系列的接口:
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock;
這些接口最終都會調(diào)用
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
新版本還給UIView增加了分類,即UIView+WebCache,最終上述方法會走到下面的方法去具體操作,比如下載圖片等。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context;
接下來對該方法進行解析
- 第一步:取消當前正在進行的異步下載,確保每個 UIImageView 對象中永遠只存在一個 operation,當前只允許一個圖片網(wǎng)絡請求,該 operation 負責從緩存中獲取 image 或者是重新下載 image。具體執(zhí)行代碼是:
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 取消先前下載的任務
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
... // 下載圖片操作
// 將生成的加載操作賦值給UIView的自定義屬性
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
上述方法定義在UIView+WebCacheOperation類中
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
// 如果之前已經(jīng)有過該圖片的下載操作,則取消之前的圖片下載操作
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; // 獲取添加在UIView的自定義屬性
id<SDWebImageOperation> operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
// 實現(xiàn)了SDWebImageOperation的協(xié)議
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
實際上,所有的操作都是由一個實際上,所有的操作都是由一個operationDictionary字典維護的,執(zhí)行新的操作之前,cancel所有的operation。
- 第二步:占位圖策略
作為圖片下載完成之前的替代圖片。dispatch_main_async_safe是一個宏,保證在主線程安全執(zhí)行。
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
// 設置占位圖
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
- 第三步:判斷url是否合法
如果url合法,則進行圖片下載操作,否則直接block回調(diào)失敗
if (url) {
// 下載圖片操作
} else {
dispatch_main_async_safe(^{
#if SD_UIKIT
[self sd_removeActivityIndicator];
#endif
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
- 第四步 下載圖片操作
下載圖片的操作是由SDWebImageManager完成的,它是一個單例
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
下載完成之后刷新UIImageView的圖片。
// 根據(jù)枚舉類型,判斷是否需要設置圖片
shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout]; // 設置圖片
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
if (shouldNotSetImage) { // 不要自動設置圖片,則調(diào)用block傳入image對象
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
// 設置圖片操作
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
#endif
callCompletedBlockClojure();
});
最后,把返回的id operation添加到operationDictionary中,方便后續(xù)的cancel。
// 將生成的加載操作賦值給UIView的自定義屬性
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
3.2 SDWebImageManager
在SDWebImageManager.h中是這樣描述SDWebImageManager類的:
/**
* The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
* It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
* You can use this class directly to benefit from web image downloading with caching in another context than
* a UIView.
*/
即隱藏在UIImageView+WebCache背后,用于處理異步下載和圖片緩存的類,當然你也可以直接使用 SDWebImageManager 的方法 來直接下載圖片。
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
SDWebImageManager.h首先定義了一些枚舉類型的SDWebImageOptions。
然后,聲明了四個block:
//操作完成的回調(diào),被上層的擴展調(diào)用。
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//被SDWebImageManager調(diào)用。如果使用了SDWebImageProgressiveDownload標記,這個block可能會被重復調(diào)用,直到圖片完全下載結束,finished=true,再最后調(diào)用一次這個block。
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//SDWebImageManager每次把URL轉(zhuǎn)換為cache key的時候調(diào)用,可以刪除一些image URL中的動態(tài)部分。
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);
定義了SDWebImageManagerDelegate協(xié)議:
@protocol SDWebImageManagerDelegate
@optional
// 控制在cache中沒有找到image時 是否應該去下載。
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
// 在下載之后,緩存之前轉(zhuǎn)換圖片。在全局隊列中操作,不阻塞主線程
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end
SDWebImageManager是單例使用的,分別維護了一個SDImageCache實例和一個SDWebImageDownloader實例。 對象方法分別是:
//初始化SDWebImageManager單例,在init方法中已經(jīng)初始化了cache單例和downloader單例。
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
//下載圖片
- (id )downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//緩存給定URL的圖片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消當前所有的操作
- (void)cancelAll;
//監(jiān)測當前是否有進行中的操作
- (BOOL)isRunning;
//監(jiān)測圖片是否在緩存中, 先在memory cache里面找 再到disk cache里面找
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
//監(jiān)測圖片是否緩存在disk里
- (BOOL)diskImageExistsForURL:(NSURL *)url;
//監(jiān)測圖片是否在緩存中,監(jiān)測結束后調(diào)用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//監(jiān)測圖片是否緩存在disk里,監(jiān)測結束后調(diào)用completionBlock
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回給定URL的cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;
我們主要研究
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
- 首先,監(jiān)測url 的合法性:
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
第一個判斷條件是防止很多用戶直接傳遞NSString作為NSURL導致的錯誤,第二個判斷條件防止crash。
2.集合failedURLs保存之前失敗的urls,如果url為空或者url之前失敗過且不采用重試策略,直接調(diào)用completedBlock返回錯誤。
BOOL isFailedUrl = NO;
if (url) { // 判斷url是否是失敗過的url
LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
UNLOCK(self.failedURLsLock);
}
// 如果url為空或者url下載失敗并且設置了不再重試
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
3.保存操作
LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock);
runningOperations是一個可變數(shù)組,保存所有的operation,主要用來監(jiān)測是否有operation在執(zhí)行,即判斷running 狀態(tài)。
4.查找緩存
SDWebImageManager會首先在memory以及disk的cache中查找是否下載過相同的照片,即調(diào)用imageCache的下面方法
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;
如果操作取消,則直接返回
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) { // operation取消,那么將下載任務從下載隊列中直接移除
[self safelyRemoveOperationFromRunning:strongOperation];
return;
}
如果沒有在緩存中找到圖片,或者不管是否找到圖片,只要operation有SDWebImageRefreshCached標記,那么若SDWebImageManagerDelegate的shouldDownloadImageForURL方法返回true,即允許下載時,都使用 imageDownloader 的下載方法
- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock
如果下載有錯誤,直接調(diào)用completedBlock返回錯誤,并且視情況將url添加到failedURLs里面;
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
if (error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
如果下載成功,若支持失敗重試,將url從failURLs里刪除:
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
如果delegate實現(xiàn)了,imageManager:transformDownloadedImage:withURL:方法,圖片在緩存之前,需要做轉(zhuǎn)換(在全局隊列中調(diào)用,不阻塞主線程)。轉(zhuǎn)化成功切下載全部結束,圖片存入緩存,調(diào)用completedBlock回調(diào),第一個參數(shù)是轉(zhuǎn)換后的image。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//將圖片緩存起來
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
否則,直接存入緩存,調(diào)用completedBlock回調(diào),第一個參數(shù)是下載的原始image。
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
存入緩存都是調(diào)用imageCache的下面方法
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk
如果沒有在緩存找到圖片,且不允許下載,直接調(diào)用completedBlock,第一個參數(shù)為nil。
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {//為啥這里用weakOperation TODO
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
最后都要將這個operation從runningOperations里刪除。
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
3.3 SDWebImageCombinedOperation
@interface SDWebImageCombinedOperation : NSObject
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
是一個遵循SDWebImageOperation協(xié)議的NSObject子類。
@protocol SDWebImageOperation
- (void)cancel;
@end
在里面封裝一個NSOperation,這么做的目的應該是為了使代碼更簡潔。因為下載操作需要查詢緩存的operation和實際下載的operation,這個類的cancel方法可以同時cancel兩個operation,同時還可以維護一個狀態(tài)cancelled。
SDWebImage 使用
1.使用IImageView+WebCache category來加載UITableView中cell的圖片
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img1.cache.netease.com/catchpic/5/51/5132C377F99EEEE927697E62C26DDFB1.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
2.使用Blocks,采用這個方案可以在網(wǎng)絡圖片加載過程中得知圖片的下載進度和圖片加載成功與否
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img1.cache.netease.com/catchpic/5/51/5132C377F99EEEE927697E62C26DDFB1.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//... completion code here ...
}];
3.使用SDWebImageManager,SDWebImageManager為UIImageView+WebCache category的實現(xiàn)提供接口。
SDWebImageManager *manager = [SDWebImageManager sharedManager] ;
[manager downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
4.加載圖片還有使用SDWebImageDownloader和SDImageCache方式
5.key的來源
// 利用Image的URL生成一個緩存時需要的key.
// 這里有兩種情況,第一種是如果檢測到cacheKeyFilter不為空時,利用cacheKeyFilter來處理URL生成一個key.
// 如果為空,那么直接返回URL的string內(nèi)容,當做key.
- (NSString *)cacheKeyForURL:(NSURL *)url {
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
}
else {
return [url absoluteString];
}
}
SDWebImage 流程

SDWebImage 接口
SDWebImage是一個成熟而且比較龐大的框架,但是在使用過程中并不需要太多的接口,這算是一種代碼封裝程度的體現(xiàn)。這里就介紹比較常用的幾個接口。
1.給UIImageView設置圖片的接口,SDWebImage有提供多個給UIImageView設置圖片的接口,最終所有的接口都會調(diào)用下圖的這個接口,這是大多數(shù)框架的做法。
///所以設置圖片的方法最終都會調(diào)用該方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
}
2.獲取SDWebImage的磁盤緩存大小,在項目中有時候會需要統(tǒng)計應用的磁盤緩存內(nèi)容大小,那么獲取圖片的緩存大小就是使用這個接口來實現(xiàn)
[SDImageCache sharedImageCache] getSize];
3.清理內(nèi)存緩存,清理內(nèi)存中緩存的圖片資源,釋放內(nèi)存資源。
[[SDImageCache sharedImageCache] clearMemory];
4.有了清理內(nèi)存緩存,自然也有清理磁盤緩存的接口
[[SDImageCache sharedImageCache] clearDisk];
SDWebImage 解析
<1>入口 setImageWithURL:placeholderImage:options: 會先把 placeholderImage 顯示,然后 SDWebImageManager 根據(jù) URL 開始處理圖片。
<2>進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:.
<3>先從內(nèi)存圖片緩存查找是否有圖片,如果內(nèi)存中已經(jīng)有圖片緩存,SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
<4>SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。
<5>如果內(nèi)存緩存中沒有,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經(jīng)緩存。
<6>根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結果回調(diào) notifyDelegate:。
<7>如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小,會先清空內(nèi)存緩存)。SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:。進而回調(diào)展示圖片。
<8>如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調(diào) imageCache:didNotFindImageForKey:userInfo:。
<9>共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
<10>圖片下載由 NSURLConnection 來做,實現(xiàn)相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
<11>connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
<12>connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
<13>圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
<14>在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader。
<15>imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成。
<16>通知所有的 downloadDelegates 下載完成,回調(diào)給需要的地方展示圖片。
<17>將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。
<18>SDImageCache 在初始化的時候會注冊一些消息通知,在內(nèi)存警告或退到后臺的時候清理內(nèi)存圖片緩存,應用結束的時候清理過期圖片。
<19>SDWebImage 也提供了 UIButton+WebCache 和MKAnnotationView+WebCache,方便使用。
<20>SDWebImagePrefetcher 可以預先下載圖片,方便后續(xù)使用。
從上面流程可以看出,當你調(diào)用setImageWithURL:方法的時候,他會自動去給你干這么多事,當你需要在某一具體時刻做事情的時候,你可以覆蓋這些方法。比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:
//覆蓋方法,指哪打哪,這個方法是下載imagePath2的時候響應
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"顯示當前進度");
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
NSLog(@"下載完成");
}];