SDWebImage是一個(gè)圖片下載的開源項(xiàng)目逢艘,由于它提供了簡(jiǎn)介的接口以及異步下載與緩存的強(qiáng)大功能,深受“猿媛“的喜愛。截止到本篇文章開始,項(xiàng)目的star數(shù)已經(jīng)超過1.6k了掖疮。今天我就對(duì)項(xiàng)目的源碼做個(gè)閱讀筆記,一方面歸納總結(jié)自己的心得颗祝,另一方面給準(zhǔn)備閱讀源碼的童鞋做點(diǎn)鋪墊工作浊闪。代碼最新版本為3.8恼布。
正如項(xiàng)目的第一句介紹一樣:
Asynchronous image downloader with cache support as a UIImageView category
SDWebImage
是個(gè)支持異步下載與緩存的UIImageView擴(kuò)展。項(xiàng)目主要提供了一下功能:
- 擴(kuò)展UIImageView, UIButton, MKAnnotationView搁宾,增加網(wǎng)絡(luò)圖片與緩存管理折汞。
- 一個(gè)異步的圖片加載器
- 一個(gè)異步的 內(nèi)存 + 磁盤 圖片緩存,擁有自動(dòng)的緩存過期處理機(jī)制盖腿。
- 支持后臺(tái)圖片解壓縮處理
- 確保同一個(gè) URL 的圖片不被多次下載
- 確保虛假的 URL 不會(huì)被反復(fù)加載
- 確保下載及緩存時(shí)爽待,主線程不被阻塞
- 使用 GCD 與 ARC
項(xiàng)目支持的圖片格式包括PNG,JEPG,GIF,WebP等等。
先看看SDWebImage
的項(xiàng)目組織架構(gòu):
SDWebImageDownloader負(fù)責(zé)維持圖片的下載隊(duì)列翩腐;
SDWebImageDownloaderOperation負(fù)責(zé)真正的圖片下載請(qǐng)求堕伪;
SDImageCache負(fù)責(zé)圖片的緩存;
SDWebImageManager是總的管理類栗菜,維護(hù)了一個(gè)SDWebImageDownloader
實(shí)例和一個(gè)SDImageCache
實(shí)例欠雌,是下載與緩存的橋梁;
SDWebImageDecoder負(fù)責(zé)圖片的解壓縮;
SDWebImagePrefetcher負(fù)責(zé)圖片的預(yù)雀沓铩富俄;
UIImageView+WebCache和其他的擴(kuò)展都是與用戶直接打交道的。
其中而咆,最重要的三個(gè)類就是SDWebImageDownloader
霍比、SDImageCache
、SDWebImageManager
暴备。接下來(lái)我們就分別詳細(xì)地研究一下這些類各自具體做了哪些事悠瞬,又是怎么做的。
為了便于大家從宏觀上有個(gè)把握涯捻,我這里先給出項(xiàng)目的框架結(jié)構(gòu):
UIImageView+WebCache
和UIButton+WebCache
直接為表層的 UIKit框架提供接口, 而 SDWebImageManger
負(fù)責(zé)處理和協(xié)調(diào)SDWebImageDownloader
和SDWebImageCache
, 并與 UIKit層進(jìn)行交互浅妆。SDWebImageDownloaderOperation
真正執(zhí)行下載請(qǐng)求;最底層的兩個(gè)類為高層抽象提供支持障癌。
我們按照從上到下執(zhí)行的流程來(lái)研究各個(gè)類
UIImageView+WebCache
這里凌外,我們只用UIImageView+WebCache
來(lái)舉個(gè)例子,其他的擴(kuò)展類似涛浙。
常用的場(chǎng)景是已知圖片的url地址康辑,來(lái)下載圖片并設(shè)置到UIImageView上。UIImageView+WebCache
提供了一系列的接口:
- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock;
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock;
這些接口最終會(huì)調(diào)用
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock轿亮;
方法的第一行代碼[self sd_cancelCurrentImageLoad]
是取消UIImageView上當(dāng)前正在進(jìn)行的異步下載疮薇,確保每個(gè) UIImageView 對(duì)象中永遠(yuǎn)只存在一個(gè) operation,當(dāng)前只允許一個(gè)圖片網(wǎng)絡(luò)請(qǐng)求我注,該 operation 負(fù)責(zé)從緩存中獲取 image 或者是重新下載 image按咒。具體執(zhí)行代碼是:
// UIView+WebCacheOperation.m
// Cancel in progress downloader from queue
NSMutableDictionary *operationDictionary = [self operationDictionary];
id operations = [operationDictionary objectForKey:key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
實(shí)際上,所有的操作都是由一個(gè)operationDictionary
字典維護(hù)的,執(zhí)行新的操作之前仓手,先cancel所有的operation胖齐。這里的cancel是SDWebImageOperation
協(xié)議里面定義的。
//預(yù)覽 占位圖
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
是一種占位圖策略嗽冒,作為圖片下載完成之前的替代圖片呀伙。dispatch_main_async_safe
是一個(gè)宏,保證在主線程安全執(zhí)行添坊,最后再講剿另。
然后判斷url,url為空就直接調(diào)用完成回調(diào)贬蛙,報(bào)告錯(cuò)誤信息雨女;否則,用SDWebImageManager
單例的
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
方法下載圖片阳准。下載完成之后刷新UIImageView的圖片氛堕。
//圖像的繪制只能在主線程完成
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{//延遲設(shè)置圖片,手動(dòng)處理
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
//直接設(shè)置圖片
wself.image = image;
[wself setNeedsLayout];
} else {
//image== nil,設(shè)置占位圖
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
最后野蝇,把返回的id <SDWebImageOperation> operation
添加到operationDictionary
中讼稚,方便后續(xù)的cancel。
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
背后绕沈,用于處理異步下載和圖片緩存的類锐想,當(dāng)然你也可以直接使用 SDWebImageManager 的方法 downloadImageWithURL:options:progress:completed:
來(lái)直接下載圖片。
SDWebImageManager.h
首先定義了一些枚舉類型的SDWebImageOptions
乍狐。關(guān)于這些Options的具體含義可以參考葉孤城大神的解析
然后赠摇,聲明了三個(gè)block:
//操作完成的回調(diào),被上層的擴(kuò)展調(diào)用浅蚪。
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//被SDWebImageManager調(diào)用藕帜。如果使用了SDWebImageProgressiveDownload標(biāo)記,這個(gè)block可能會(huì)被重復(fù)調(diào)用惜傲,直到圖片完全下載結(jié)束耘戚,finished=true,再最后調(diào)用一次這個(gè)block。
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//SDWebImageManager每次把URL轉(zhuǎn)換為cache key的時(shí)候調(diào)用操漠,可以刪除一些image URL中的動(dòng)態(tài)部分收津。
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
定義了SDWebImageManagerDelegate
協(xié)議:
@protocol SDWebImageManagerDelegate <NSObject>
@optional
/**
* Controls which image should be downloaded when the image is not found in the cache.
*
* @param imageManager The current `SDWebImageManager`
* @param imageURL The url of the image to be downloaded
*
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
* 控制在cache中沒有找到image時(shí) 是否應(yīng)該去下載。
*/
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
* NOTE: This method is called from a global queue in order to not to block the main thread.
*
* @param imageManager The current `SDWebImageManager`
* @param image The image to transform
* @param imageURL The url of the image to transform
*
* @return The transformed image object.
* 在下載之后浊伙,緩存之前轉(zhuǎn)換圖片撞秋。在全局隊(duì)列中操作,不阻塞主線程
*/
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end
SDWebImageManager
是單例使用的嚣鄙,分別維護(hù)了一個(gè)SDImageCache
實(shí)例和一個(gè)SDWebImageDownloader
實(shí)例吻贿。 類方法分別是:
//初始化SDWebImageManager單例,在init方法中已經(jīng)初始化了cache單例和downloader單例哑子。
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
//下載圖片
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//緩存給定URL的圖片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消當(dāng)前所有的操作
- (void)cancelAll;
//監(jiān)測(cè)當(dāng)前是否有進(jìn)行中的操作
- (BOOL)isRunning;
//監(jiān)測(cè)圖片是否在緩存中舅列, 先在memory cache里面找 再到disk cache里面找
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
//監(jiān)測(cè)圖片是否緩存在disk里
- (BOOL)diskImageExistsForURL:(NSURL *)url;
//監(jiān)測(cè)圖片是否在緩存中,監(jiān)測(cè)結(jié)束后調(diào)用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//監(jiān)測(cè)圖片是否緩存在disk里,監(jiān)測(cè)結(jié)束后調(diào)用completionBlock
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回給定URL的cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;
我們主要研究
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
首先肌割,監(jiān)測(cè)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;
}
第一個(gè)判斷條件是防止很多用戶直接傳遞NSString作為NSURL導(dǎo)致的錯(cuò)誤,第二個(gè)判斷條件防止crash帐要。
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}
集合failedURLs
保存之前失敗的urls把敞,如果url為空或者url之前失敗過且不采用重試策略,直接調(diào)用completedBlock返回錯(cuò)誤榨惠。
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
runningOperations
是一個(gè)可變數(shù)組奋早,保存所有的operation,主要用來(lái)監(jiān)測(cè)是否有operation在執(zhí)行赠橙,即判斷running 狀態(tài)耽装。
SDWebImageManager
會(huì)首先在memory以及disk的cache中查找是否下載過相同的照片,即調(diào)用imageCache的
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock
方法期揪。
如果在緩存中找到圖片掉奄,直接調(diào)用completedBlock,第一個(gè)參數(shù)是緩存的image凤薛。
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {//為啥這里用strongOperation TODO
completedBlock(image, nil, cacheType, YES, url);
}
});
如果沒有在緩存中找到圖片挥萌,或者不管是否找到圖片,只要operation有SDWebImageRefreshCached
標(biāo)記枉侧,那么若SDWebImageManagerDelegate
的shouldDownloadImageForURL
方法返回true引瀑,即允許下載時(shí),都使用 imageDownloader 的
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock
方法進(jìn)行下載榨馁。如果下載有錯(cuò)誤憨栽,直接調(diào)用completedBlock返回錯(cuò)誤,并且視情況將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實(shí)現(xiàn)了珍剑,imageManager:transformDownloadedImage:withURL:
方法掸宛,圖片在緩存之前,需要做轉(zhuǎn)換(在全局隊(duì)列中調(diào)用招拙,不阻塞主線程)唧瘾。轉(zhuǎn)化成功切下載全部結(jié)束,圖片存入緩存别凤,調(diào)用completedBlock回調(diào)饰序,第一個(gè)參數(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];
//將圖片緩存起來(lái)
[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),第一個(gè)參數(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蚤告,第一個(gè)參數(shù)為nil努酸。
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {//為啥這里用weakOperation TODO
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
最后都要將這個(gè)operation從runningOperations里刪除。
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
這里再說(shuō)一下上面的operation罩缴,是一個(gè)SDWebImageCombinedOperation
實(shí)例:
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
是一個(gè)遵循SDWebImageOperation
協(xié)議的NSObject子類。
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end
在里面封裝一個(gè)NSOperation层扶,這么做的目的應(yīng)該是為了使代碼更簡(jiǎn)潔箫章。因?yàn)橄螺d操作需要查詢緩存的operation和實(shí)際下載的operation,這個(gè)類的cancel方法可以同時(shí)cancel兩個(gè)operation镜会,同時(shí)還可以維護(hù)一個(gè)狀態(tài)cancelled檬寂。
敬請(qǐng)期待后續(xù)更新!