1、概述
SDWebImage基本是iOS項目的標配。他以靈活簡單的api,提供了圖片從加載、解析、處理、緩存、清理等一些列功能。讓我們專心于業(yè)務的處理。但是并不意味著會用就可以了,通過源碼分析和學習,讓我們知道如何用好它。學習分析優(yōu)秀源碼也可以從潛移默化中給我們提供很多解決日常需求的思路。下面就是一張圖來概述SDWebImage的所有類:

通過對這個圖片的分析,我們可以把SDWebImage的源碼分為三種:
- 各種分類:
-
UIButton(WebCache)為UIButton類添加載圖片的方法。比如正常情況下、點擊情況下、的image屬性和背景圖片等。 -
MKAnnotationView(WebCache)為MKAnnotationView類添加各種加載圖片的方法。 -
UIImageView(WebCache)為UIImageView類添加加載圖片的方法。 -
UIImageView(HighlightedWebCache)為UIImageView`類添加高亮狀態(tài)下加載圖片的方法。 -
FLAnimatedImageView(WebCache)為FLAnimatedImageView類添加加載動態(tài)的方法,這個分類需要引入FLAnimatedImage框架。SDWebImage推薦使用這個框架來處理動態(tài)圖片(GIF)的加載。 -
UIImageView、UIButton、FLAnimatedImageView通過sd_setImageWithURL等api來做圖片加載請求。這也是我們唯一需要做的。 - 上面的幾個
UIView子類都會調(diào)用UIView(WebCache)分類的sd_internalSetImageWithURL方法來做圖片加載請求。具體是通過SDWebImageManager調(diào)用來實現(xiàn)的。同時實現(xiàn)了 Operation取消、ActivityIndicator的添加與取消。
-
- 各種工具類:
-
NSData+ImageContentType: 根據(jù)圖片數(shù)據(jù)獲取圖片的類型,比如GIF、PNG等。 -
SDWebImageCompat: 根據(jù)屏幕的分辨倍數(shù)成倍放大或者縮小圖片大小。 -
SDImageCacheConfig: 圖片緩存策略記錄。比如是否解壓縮、是否允許iCloud、是否允許內(nèi)存緩存、緩存時間等。默認的緩存時間是一周。 -
UIImage+MultiFormat: 獲取UIImage對象對應的data、或者根據(jù)data生成指定格式的UIImage,其實就是UIImage和NSData之間的轉(zhuǎn)換處理。 -
UIImage+GIF: 對于一張圖片是否GIF做判斷??梢愿鶕?jù)NSData返回一張GIF的UIImage對象,并且只返回GIF的第一張圖片生成的GIF。如果要顯示多張GIF,使用FLAnimatedImageView。 -
SDWebImageDecoder: 根據(jù)圖片的情況,做圖片的解壓縮處理。并且根據(jù)圖片的情況決定如何處理解壓縮。
-
- 核心類:
-
SDImageCache負責SDWebImage的整個緩存工作,是一個單列對象。緩存路徑處理、緩存名字處理、管理內(nèi)存緩存和磁盤緩存的創(chuàng)建和刪除、根據(jù)指定key獲取圖片、存入圖片的類型處理、根據(jù)緩存的創(chuàng)建和修改日期刪除緩存。 -
SDWebImageManager: 擁有一個SDWebImageCache和SDWebImageDownloader屬性分別用于圖片的緩存和加載處理。為UIView及其子類提供了加載圖片的統(tǒng)一接口。管理正在加載操作的集合。這個類是一個單列。還有就是各種加載選項的處理。 -
SDWebImageDownloader: 實現(xiàn)了圖片加載的具體處理,如果圖片在緩存存在則從緩存區(qū)。如果緩存不存在,則直接創(chuàng)建一個。 SDWebImageDownloaderOperation對象來下載圖片。管理NSURLRequest對象請求頭的封裝、緩存、cookie的設置。加載選項的處理等功能。管理Operation之間的依賴關系。這個類是一個單列. -
SDWebImageDownloaderOperation: 一個自定義的并行Operation子類。這個類主要實現(xiàn)了圖片下載的具體操作、以及圖片下載完成以后的圖片解壓縮、Operation生命周期管理等。 -
UIView+WebCache: 所有的UIButton、UIImageView都回調(diào)用這個分類的方法來完成圖片加載的處理。同時通過UIView+WebCacheOperation分類來管理請求的取消和記錄工作。所有 UIView及其子類的分類都是用這個類的sd_intemalSetImageWithURL:來實現(xiàn)圖片的加載。 -
FLAnimatedImageView: 動態(tài)圖片的數(shù)據(jù)通過ALAnimatedImage對象來封裝。-FLAnimatedImageView是UIImageView的子類。通過他完全可以實現(xiàn)動態(tài)圖片的加載顯示和管理。并且比UIImageView做了流程優(yōu)化。
-
2、實現(xiàn)流程
SDWebImage為我們實現(xiàn)了圖片加載、數(shù)據(jù)處理、圖片緩存等一些列工作。通過下圖我們可以分析一下他的流程:

通過這個圖,我們發(fā)現(xiàn)SDWebImage加載的過程是首先從緩存中加載數(shù)據(jù)。而且緩存加載又是優(yōu)先從內(nèi)存緩存中加載,然后才是磁盤加載。最后如果緩存沒有,才從網(wǎng)絡上加載。同時網(wǎng)絡成功加載圖片以后,存入本地緩存。
3、UIView+WebCache 、UIView+WebCacheOperation分析
UIImageView、UIButton、FLAnimatedImageView都會調(diào)用UIView(WebCache)分類的sd_internalSetImageWithURL方法來做圖片加載請求。具體是通過SDWebImageManager調(diào)用來實現(xiàn)的。同時實現(xiàn)了Operation取消、ActivityIndicator的添加與取消。我們首先來看sd_internalSetImageWithURL方法的實現(xiàn):
/**
《核心方法》
使用image的url和占位符設置imageView的image
@param url 圖像的URL
@param placeholder 初始化的占位圖,直到url請求完成
@param options 下載圖片時的選項,詳見 SDWebImageOptions
@param operationKey 操作的關鍵字,如果為空,則為類的名字
@param setImageBlock 設置圖片時的回調(diào)代碼
@param progressBlock 圖片下載時的回調(diào),注意執(zhí)行是在后臺的隊列
@param completedBlock 所有操作完成時回調(diào),不返回值
*并將請求的uiimage作為第一個參數(shù)。如果出現(xiàn)錯誤,圖像參數(shù)
*為零,第二個參數(shù)可能包含nserrror。第三個參數(shù)是布爾值
*指示是從本地緩存還是從網(wǎng)絡檢索圖像。
*第四個參數(shù)是原始圖像URL。
@param context 具有執(zhí)行指定更改或進程的額外信息的上下文。
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
internalSetImageBlock:(nullable SDInternalSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context {
/**
1、停止所有和這個View相關的Operation。
這里還有一個operation 字典:UIView+WebCacheOperation 有一個關聯(lián)對象,用來保存這個View相關的Operation
@param operationKey 操作的關鍵字,如果為空,則為類的名字
UIView+WebCacheOperation 分類用來保存view相關的操作
*/
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/** 存url
* 把UIImageView的加載圖片操作和他自身用關聯(lián)對象關聯(lián)起來,方便后面取消等操作。關聯(lián)的key就是UIImageView對應的類名
*/
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/** 如果有設置SDWebImageDelayPlaceholder,則先顯示站位圖 */
dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];
if (!(options & SDWebImageDelayPlaceholder)) {
if (group) {
dispatch_group_enter(group);
}
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
#if SD_UIKIT
// check if activityView is enabled or not
/** 如果UIImageView對象有設置添加轉(zhuǎn)動菊花數(shù)據(jù),加載的時候添加轉(zhuǎn)動的菊花 */
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
#endif
// reset the progress
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
SDWebImageManager *manager = [context objectForKey:SDWebImageExternalCustomManagerKey];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}
// 定義完成進度的block回調(diào)
__weak __typeof(self)wself = self;
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
if (!sself) { return; }
#if SD_UIKIT
//停止菊花
[sself sd_removeActivityIndicator];
#endif
// if the progress not been updated, mark it to complete state
/** 如果progress沒有更新,設置總量和完成量為未知狀態(tài) */
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
/** 如果設置了不自動顯示圖片,則直接調(diào)用completedBlock,讓調(diào)用者處理圖片的顯示
*
* finished or SDWebImageAvoidAutoSetImage --->應該 調(diào)用CompletedBlock
*/
BOOL 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);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
/**
* 得到image and (SDWebImageAvoidAutoSetImage在completeblock 中設置圖片)---> 不自動顯示
* 沒得到image and (默認顯示了SDWebImageDelayPlaceholder占位圖) ----> 不自動顯示。
* 不自動顯示 ---> 代用completedBlock
*/
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
/** ? 設置imageview的image */
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
/**
* 得到image and 沒有設置SDWebImageAvoidAutoSetImage
*/
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
/**
* 沒得到image and 沒有設置SDWebImageDelayPlaceholder
*/
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
#endif
dispatch_main_async_safe(^{
if (group) {
dispatch_group_enter(group);
}
#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 cacheType:cacheType imageURL:imageURL];
#endif
if (group) {
// compatible code for FLAnimatedImage, because we assume completedBlock called after image was set. This will be removed in 5.x
BOOL shouldUseGroup = [objc_getAssociatedObject(group, &SDWebImageInternalSetImageGroupKey) boolValue];
if (shouldUseGroup) {
dispatch_group_notify(group, dispatch_get_main_queue(), callCompletedBlockClojure);
} else {
callCompletedBlockClojure();
}
} else {
callCompletedBlockClojure();
}
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} 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);
}
});
}
}
還有就是通過
UIView+WebCacheOperation類來實現(xiàn)UIView的圖片下載Operation的關聯(lián)和取消。具體key的值可以從sd_internalSetImageWithURL中找到具體獲取方式,通過在這個方法中實現(xiàn)Operation的關聯(lián)與取消。
/**
關聯(lián)Operation對象與key對象
@param operation Operation對象
@param key key
*/
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
/**
取消當前key對應的所有實現(xiàn)了SDWebImageOperation協(xié)議的Operation對象
@param key Operation對應的key
*/
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}