話說(shuō)想要提高必須站在巨人的肩膀上,最近沒(méi)事就研究一下SDWebImage的源碼
SDWebImage 是我們經(jīng)常使用的一個(gè)異步圖片加載庫(kù),在項(xiàng)目中使用SDWebImage來(lái)管理圖片加載相關(guān)操作可以極大地提高開(kāi)發(fā)效率,讓我們更加專注于業(yè)務(wù)邏輯實(shí)現(xiàn)。
SDWebImage的實(shí)現(xiàn)流程
SDWebimage目錄結(jié)構(gòu)
實(shí)現(xiàn)原理
SDWebImage 是由一個(gè) SDImageCache
(一個(gè)處理緩存的類(lèi)) 和 SDWebImageDownloader
(負(fù)責(zé)下載網(wǎng)絡(luò)圖片) ,而 SDWebImageManager
則是管理者將前兩者結(jié)合起來(lái)完成整個(gè)工作流程。
1 、入口 setImageWithURL:placeholderImage:options:
會(huì)先把 placeholderImage
顯示,然后SDWebImageManager
根據(jù) URL 開(kāi)始處理圖片。
2、進(jìn)入 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)存緩存中沒(méi)有,生成 NSInvocationOperation
添加到隊(duì)列開(kāi)始從硬盤(pán)查找圖片是否已經(jīng)緩存。
6、根據(jù) URLKey 在硬盤(pán)緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:
。
7、如果上一操作從硬盤(pán)讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過(guò)小,會(huì)先清空內(nèi)存緩存)。SDImageCacheDelegate
回調(diào) imageCache:didFindImage:forKey:userInfo:
。進(jìn)而回調(diào)展示圖片。
8、如果從硬盤(pán)緩存目錄讀取不到圖片,說(shuō)明所有緩存都不存在該圖片,需要下載圖片,回調(diào) imageCache:didNotFindImageForKey:userInfo:
。
9、共享或重新生成一個(gè)下載器 SDWebImageDownloader 開(kāi)始下載圖片。圖片下載由 NSURLConnection 來(lái)做,實(shí)現(xiàn)相關(guān) delegate 來(lái)判斷圖片下載中、下載完成和下載失敗。
10、connection:didReceiveData:
中利用ImageIO
做了按圖片下載進(jìn)度加載效果。
connectionDidFinishLoading:
數(shù)據(jù)下載完成后交給 SDWebImageDecoder
做圖片解碼處理。
11、圖片解碼處理在一個(gè) NSOperationQueue
完成,不會(huì)拖慢主線程 UI。如果有需要對(duì)下載的圖片進(jìn)行二次處理,最好也在這里完成,效率會(huì)好很多。
在主線程 notifyDelegateOnMainThreadWithInfo:
宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo:
回調(diào)給 SDWebImageDownloader
。
imageDownloader:didFinishWithImage:
回調(diào)給 SDWebImageManager
告知圖片下載完成。
通知所有的downloadDelegates
下載完成,回調(diào)給需要的地方展示圖片。
12、將圖片保存到 SDImageCache
中,內(nèi)存緩存和硬盤(pán)緩存同時(shí)保存。寫(xiě)文件到硬盤(pán)也在以單獨(dú)NSInvocationOperation
完成,避免拖慢主線程。
SDImageCache 在初始化的時(shí)候會(huì)注冊(cè)一些消息通知,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時(shí)候清理過(guò)期圖片。
SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
SDWebImagePrefetcher
可以預(yù)先下載圖片,方便后續(xù)使用。
SDWebImageManager管理類(lèi)
SDWebImageManager管理類(lèi)中一共有12個(gè)枚舉方法,3個(gè)回調(diào)的block,2個(gè)代理方法,8個(gè)成員方法
12個(gè)枚舉方法
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* 默認(rèn)情況下,如果一個(gè)url在下載的時(shí)候失敗了,那么這個(gè)url會(huì)被加入黑名單,不會(huì)嘗試再次下載。如果使用該參數(shù),則該URL不會(huì)被添加到黑名單中。意味著會(huì)對(duì)下載失敗的URL嘗試重新下載。
* 此標(biāo)記取消黑名單
*/
SDWebImageRetryFailed = 1 << 0, //失敗后嘗試重新下載
/**
* 默認(rèn)情況下,在 UI 交互時(shí)也會(huì)啟動(dòng)圖像下載,此標(biāo)記取消這一特性
* 會(huì)推遲到滾動(dòng)視圖停止?jié)L動(dòng)之后再繼續(xù)下載
* 備注:NSURLConnection 的網(wǎng)絡(luò)下載事件監(jiān)聽(tīng)的運(yùn)行循環(huán)模式是 NSDefaultRunLoopMode
*/
SDWebImageLowPriority = 1 << 1, //低優(yōu)先級(jí)
SDWebImageCacheMemoryOnly = 1 << 2, //只使用內(nèi)存緩存
SDWebImageProgressiveDownload = 1 << 3, //漸進(jìn)式下載
/**
* 遵守 HTPP 響應(yīng)的緩存控制,如果需要,從遠(yuǎn)程刷新圖像
* 磁盤(pán)緩存將由 NSURLCache 處理,而不是 SDWebImage,這會(huì)對(duì)性能有輕微的影響
* 此選項(xiàng)用于處理URL指向圖片發(fā)生變化的情況
* 如果緩存的圖像被刷新,會(huì)調(diào)用一次 completion block,并傳遞最終的圖像
*/
SDWebImageRefreshCached = 1 << 4, //刷新緩存
SDWebImageContinueInBackground = 1 << 5, //后臺(tái)下載
SDWebImageHandleCookies = 1 << 6, //處理保存在NSHTTPCookieStore中的cookies
SDWebImageAllowInvalidSSLCertificates = 1 << 7, //允許不信任的 SSL 證書(shū)
/**
* 默認(rèn)情況下,圖像會(huì)按照添加到隊(duì)列中的順序被加載,此標(biāo)記會(huì)將它們移動(dòng)到隊(duì)列前端被立即加載
* 而不是等待當(dāng)前隊(duì)列被加載,因?yàn)榈却?duì)列加載會(huì)需要一段時(shí)間
*/
SDWebImageHighPriority = 1 << 8, //高優(yōu)先級(jí)(優(yōu)先下載)
/**
* 默認(rèn)情況下,在加載圖像時(shí),占位圖像已經(jīng)會(huì)被加載。
* 此標(biāo)記會(huì)延遲加載占位圖像,直到圖像已經(jīng)完成加載
*/
SDWebImageDelayPlaceholder = 1 << 9, //延遲占位圖片
SDWebImageTransformAnimatedImage = 1 << 10, //轉(zhuǎn)換動(dòng)畫(huà)圖像
SDWebImageAvoidAutoSetImage = 1 << 11 //手動(dòng)設(shè)置圖像
};
3個(gè)block回調(diào)
//定義任務(wù)完成的block塊
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//定義任務(wù)結(jié)束的block塊
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//定義緩存過(guò)濾器的block塊
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
2個(gè)代理
/**
* 如果該圖片沒(méi)有緩存,那么下載
*
* @param imageManager:當(dāng)前的SDWebImageManager
* @param imageURL:要下載圖片的URL地址
*
* @return 如果要下載的圖片在緩存中不存在,則返回NO,否則返回YES
*/
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**
* 允許在下載后立即將圖像轉(zhuǎn)換,并進(jìn)行磁盤(pán)和內(nèi)存緩存。
*
* @param imageManager 當(dāng)前的SDWebImageManager
* @param image 要轉(zhuǎn)換你的圖片
* @param imageURL 要轉(zhuǎn)換的圖片的URL地址
*
* @return 變換后的圖片對(duì)象
*/
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
成員方法
該方法是管理類(lèi)中最重要的方法
/* 如果URL對(duì)應(yīng)的圖像在緩存中不存在,那么就下載指定的圖片 ,否則返回緩存的圖像
*
* @param url 圖片的URL地址
* @param options 指定此次請(qǐng)求策略的選項(xiàng)
* @param progressBlock 圖片下載進(jìn)度的回調(diào)
* @param completedBlock 操作完成后的回調(diào)
* 此參數(shù)是必須的,此block沒(méi)有返回值
* Image:請(qǐng)求的 UIImage,如果出現(xiàn)錯(cuò)誤,image參數(shù)是nil
* error:如果出現(xiàn)錯(cuò)誤,則error有值
* cacheType:`SDImageCacheType` 枚舉,標(biāo)示該圖像的加載方式
* SDImageCacheTypeNone:從網(wǎng)絡(luò)下載
* SDImageCacheTypeDisk:從本地緩存加載
* SDImageCacheTypeMemory:從內(nèi)存緩存加載
* finished:如果圖像下載完成則為YES,如果使用 SDWebImageProgressiveDownload 選項(xiàng),同時(shí)只獲取到部分圖片時(shí),返回 NO
* imageURL:圖片的URL地址
*
* @return SDWebImageOperation對(duì)象,應(yīng)該是SDWebimageDownloaderOperation實(shí)例
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBloc
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
方法解釋
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 沒(méi)有completedblock,那么調(diào)用這個(gè)方法是毫無(wú)意義的
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//檢查用戶傳入的URL是否正確,如果該URL是NSString類(lèi)型的,那么嘗試轉(zhuǎn)換
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
//防止因參數(shù)類(lèi)型錯(cuò)誤而導(dǎo)致應(yīng)用程序崩潰,判斷URL是否是NSURL類(lèi)型的,如果不是則直接設(shè)置為nil
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//初始化一個(gè)SDWebImageCombinedOperationBlock塊
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
if (url) {
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
//如果url不正確或者 選擇的下載策略不是『下載失敗嘗試重新下載』且該URL存在于黑名單中,那么直接返回,回調(diào)任務(wù)完成block塊,傳遞錯(cuò)誤信息
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;
}
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//得到該URL對(duì)應(yīng)的緩存KEY
NSString *key = [self cacheKeyForURL:url];
//該方法查找URLKEY對(duì)應(yīng)的圖片緩存是否存在,查找完畢之后把該圖片(存在|不存在)和該圖片的緩存方法以block的方式傳遞
//緩存情況查找完畢之后,在block塊中進(jìn)行后續(xù)處理(如果該圖片沒(méi)有緩存·下載|如果緩存存在|如果用戶設(shè)置了下載的緩存策略是刷新緩存如何處理等等)
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (cachedImage && options & SDWebImageRefreshCached) {
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//核心方法:使用下載器,下載圖片
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
//如果是取消操作,就什么也不做
} else if (error) {
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
//如果下載失敗,則處理結(jié)束的回調(diào),在合適的情況下把對(duì)應(yīng)圖片的URL添加到黑名單中
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否要進(jìn)行磁盤(pán)緩存?
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
//否則,如果下載圖片存在且(不是可動(dòng)畫(huà)圖片數(shù)組||下載策略為SDWebImageTransformAnimatedImage&&transformDownloadedImage方法可用)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//在下載后立即將圖像轉(zhuǎn)換,并進(jìn)行磁盤(pán)和內(nèi)存緩存
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//在主線程中回調(diào)completedBlock
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
if (downloadedImage && finished) {
//得到下載的圖片且已經(jīng)完成,則進(jìn)行緩存處理
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
// 下載結(jié)束,移除隊(duì)列
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//取消操作
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
//緩存
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
其他方法
/**
* 根據(jù)圖片的URL保存圖片到緩存
*
* @param image:緩存的圖片
* @param url:該圖片的URL地址
*/
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
/**
* 取消當(dāng)前所有操作
*/
- (void)cancelAll;
/**
* 檢查一個(gè)或多個(gè)操作是否正在運(yùn)行
*/
- (BOOL)isRunning;
/*
* 檢查圖像是否已經(jīng)被緩存,如果已經(jīng)緩存則返回YES
* @param url:圖片對(duì)應(yīng)的URL
*
* @return 返回是否存在的BOOL值
*/
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
/**
* 檢查圖像是否存在磁盤(pán)緩存(此方法僅針對(duì)磁盤(pán)進(jìn)行檢查,只要存在就返回YES)
*
* @param url 圖片的url
*
* @return 是否存在(只檢查磁盤(pán)緩存)
*/
- (BOOL)diskImageExistsForURL:(NSURL *)url;
/**
* 異步檢查圖像是否已經(jīng)有內(nèi)存緩存
*
* @param URL 圖片對(duì)應(yīng)的URL
* @param completionBlock 當(dāng)任務(wù)執(zhí)行完畢之后調(diào)用的block
* @note completionBlock始終在主隊(duì)列執(zhí)行
*/
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* 異步檢查圖像是否已經(jīng)有磁盤(pán)緩存
*
* @param URL 圖片對(duì)應(yīng)的URL
* @param completionBlock 當(dāng)任務(wù)執(zhí)行完畢之后調(diào)用的block
* @note completionBlock始終在主隊(duì)列執(zhí)行
*/
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* 返回指定URL的緩存鍵值,就是URL字符串
*/
- (NSString *)cacheKeyForURL:(NSURL *)url;
SDWebImageDownloader下載類(lèi)
SDWebImageDownloader的頭文件內(nèi)容比較少,主要是定義了一些基本參數(shù)如下載優(yōu)先級(jí)策略、最大并發(fā)數(shù)、超時(shí)時(shí)間等
SDWebImageDownloader 中核心方法:下載圖片的操作
/*
* 使用給定的 URL 創(chuàng)建 SDWebImageDownloader 異步下載器實(shí)例
* 圖像下載完成或者出現(xiàn)錯(cuò)誤時(shí)會(huì)通知代理
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self; //為了避免block的循環(huán)引用
//處理進(jìn)度回調(diào)|完成回調(diào)等,如果該url在self.URLCallbacks并不存在,則調(diào)用createCallback block塊
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
//處理下載超時(shí),如果沒(méi)有設(shè)置過(guò)則初始化為15秒
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
//根據(jù)給定的URL和緩存策略創(chuàng)建可變的請(qǐng)求對(duì)象,設(shè)置請(qǐng)求超時(shí)
//請(qǐng)求策略:如果是SDWebImageDownloaderUseNSURLCache則使用NSURLRequestUseProtocolCachePolicy,否則使用NSURLRequestReloadIgnoringLocalCacheData
/*
NSURLRequestUseProtocolCachePolicy:默認(rèn)的緩存策略
1)如果緩存不存在,直接從服務(wù)端獲取。
2)如果緩存存在,會(huì)根據(jù)response中的Cache-Control字段判斷下一步操作,如: Cache-Control字段為must-revalidata, 則詢問(wèn)服務(wù)端該數(shù)據(jù)是否有更新,無(wú)更新的話直接返回給用戶緩存數(shù)據(jù),若已更新,則請(qǐng)求服務(wù)端.
NSURLRequestReloadIgnoringLocalCacheData:忽略本地緩存數(shù)據(jù),直接請(qǐng)求服務(wù)端。
*/
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
//設(shè)置是否使用Cookies(采用按位與)
/*
關(guān)于cookies參考:http://blog.csdn.net/chun799/article/details/17206907
*/
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
//開(kāi)啟HTTP管道,這可以顯著降低請(qǐng)求的加載時(shí)間,但是由于沒(méi)有被服務(wù)器廣泛支持,默認(rèn)是禁用的
request.HTTPShouldUsePipelining = YES;
//設(shè)置請(qǐng)求頭信息(過(guò)濾等)
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
//核心方法:創(chuàng)建下載圖片的操作
operation = [[wself.operationClass alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
//遍歷callbacksForURL數(shù)組中的所有字典,執(zhí)行SDWebImageDownloaderProgressBlock回調(diào)
for (NSDictionary *callbacks in callbacksForURL) {
//說(shuō)明:SDWebImageDownloaderProgressBlock作者可能考慮到用戶拿到進(jìn)度數(shù)據(jù)后會(huì)進(jìn)行刷新處理,因此在主線程中處理了回調(diào)
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
} completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
//如果完成,那么把URL從URLCallbacks字典中刪除
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
//遍歷callbacksForURL數(shù)組中的所有字典,執(zhí)行SDWebImageDownloaderCompletedBlock回調(diào)
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
} cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
//把當(dāng)前的url從URLCallbacks字典中移除
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
//設(shè)置是否需要解碼
operation.shouldDecompressImages = wself.shouldDecompressImages;
//身份認(rèn)證
if (wself.urlCredential) {
operation.credential = wself.urlCredential;
} else if (wself.username && wself.password) {
//設(shè)置 https 訪問(wèn)時(shí)身份驗(yàn)證使用的憑據(jù)
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}
//判斷下載策略是否是高優(yōu)先級(jí)的或低優(yōu)先級(jí),以設(shè)置操作的隊(duì)列優(yōu)先級(jí)
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//把下載操作添加到下載隊(duì)列中
//該方法會(huì)調(diào)用operation內(nèi)部的start方法開(kāi)啟圖片的下載任務(wù)
[wself.downloadQueue addOperation:operation];
//判斷任務(wù)的執(zhí)行優(yōu)先級(jí),如果是后進(jìn)先出,則調(diào)整任務(wù)的依賴關(guān)系,優(yōu)先執(zhí)行當(dāng)前的(最后添加)任務(wù)
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;//設(shè)置當(dāng)前下載操作為最后一個(gè)操作
}
}];
return operation;
}
SDImageCache緩存
SDWebImage實(shí)現(xiàn)了內(nèi)存緩存和磁盤(pán)緩存,內(nèi)存緩存是通過(guò)NSCache實(shí)現(xiàn),磁盤(pán)緩存是通過(guò)NSFileManager來(lái)實(shí)現(xiàn)文件的存儲(chǔ),磁盤(pán)緩存是異步實(shí)現(xiàn)的。
核心的方法就是對(duì)圖片緩存的增、刪、查的實(shí)現(xiàn)
/**
* 使用指定的命名空間實(shí)例化一個(gè)新的緩存存儲(chǔ)
* @param ns 緩存存儲(chǔ)使用的命名空間
*/
- (id)initWithNamespace:(NSString *)ns;
/**
* 使用指定的命名空間實(shí)例化一個(gè)新的緩存存儲(chǔ)和目錄
* @param ns 緩存存儲(chǔ)使用的命名空間
* @param directory 緩存映像所在目錄
*/
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
//設(shè)置磁盤(pán)緩存路徑
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
/*
* 如果希望在 bundle 中存儲(chǔ)預(yù)加載的圖像,可以添加一個(gè)只讀的緩存路徑
* 讓 SDImageCache 從 Bundle 中搜索預(yù)先緩存的圖像
* @param path 只讀緩存路徑(mainBundle中的全路徑)
*/
- (void)addReadOnlyCachePath:(NSString *)path;
/**
* 使用指定的鍵將圖像保存到內(nèi)存和磁盤(pán)緩存
*
* @param image 要保存的圖片
* @param key 唯一的圖像緩存鍵,通常是圖像的完整 URL
*/
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
/**
* 使用指定的鍵將圖像保存到內(nèi)存和可選的磁盤(pán)緩存
*
* @param image 要保存的圖片
* @param key 唯一的圖像緩存鍵,通常是圖像的完整 URL
* @param toDisk 如果是 YES,則將圖像緩存到磁盤(pán)
*/
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
* 使用指定的鍵將圖像保存到內(nèi)存和可選的磁盤(pán)緩存
*
* @param image 要保存的圖像
* @param recalculate 是否直接使用 imageData,還是從 UIImage 重新構(gòu)造數(shù)據(jù)
* @param imageData 從服務(wù)器返回圖像的二進(jìn)制數(shù)據(jù),表示直接保存到磁盤(pán)
而不是將給定的圖像對(duì)象轉(zhuǎn)換成一個(gè)可存儲(chǔ)/可壓縮的圖像格式,從而保留圖片質(zhì)量并降低 CPU 開(kāi)銷(xiāo)
* @param key 唯一的圖像緩存鍵,通常是圖像的完整 URL
* @param toDisk 如果是 YES,則將圖像緩存到磁盤(pán)
*/
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
* 異步查詢磁盤(pán)緩存
*
* @param key 保存圖像的唯一鍵
* @param doneBlock 查詢結(jié)束后的回調(diào)
*/
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
/**
* 同步查詢內(nèi)存緩存
*
* @param key 保存圖像的唯一鍵
*/
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
/**
* 查詢內(nèi)存緩存之后同步查詢磁盤(pán)緩存
*
* @param key 保存圖像的唯一鍵
*/
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
/**
* 同步從內(nèi)存和磁盤(pán)緩存刪除圖像
*
* @param key 保存圖像的唯一鍵
*/
- (void)removeImageForKey:(NSString *)key;
/**
* 同步從內(nèi)存和磁盤(pán)緩存刪除圖像
*
* @param key 保存圖像的唯一鍵
* @param completion 當(dāng)圖片被刪除后會(huì)調(diào)用該block塊
*/
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
/**
* 同步從內(nèi)存和可選磁盤(pán)緩存刪除圖像
*
* @param key 保存圖像的唯一鍵
* @param fromDisk 如果是 YES,則從磁盤(pán)刪除緩存
*/
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
/**
* 同步從內(nèi)存和可選磁盤(pán)緩存刪除圖像
*
* @param key 保存圖像的唯一鍵
* @param fromDisk 如果是 YES,則從磁盤(pán)刪除緩存
* @param completion 當(dāng)圖片被刪除后會(huì)調(diào)用該block塊
*/
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
/**
* 刪除所有內(nèi)存緩存的圖像
*/
- (void)clearMemory;
/**
* 刪除所有磁盤(pán)緩存的圖像。
* @param completion 刪除操作后的塊代碼回調(diào)(可選)
*/
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
/**
* 刪除所有磁盤(pán)緩存的圖像
* @see clearDiskOnCompletion:方法
*/
- (void)clearDisk;
/**
* 從磁盤(pán)中刪除所有過(guò)期的緩存圖像。
* @param completion 刪除操作后的塊代碼回調(diào)(可選)
*/
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
/**
* 從磁盤(pán)中刪除所有過(guò)期的緩存圖像
* @see cleanDiskWithCompletionBlock:方法
*/
- (void)cleanDisk;
/**
* 獲得磁盤(pán)緩存占用空間
*/
- (NSUInteger)getSize;
/**
* 獲得磁盤(pán)緩存圖像的個(gè)數(shù)
*/
- (NSUInteger)getDiskCount;
/**
* 異步計(jì)算磁盤(pán)緩存的大小
*/
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
/**
* 異步檢查圖像是否已經(jīng)在磁盤(pán)緩存中存在(不加載圖像)
* @param key 保存圖像的唯一鍵
* @param completionBlock 當(dāng)圖片被刪除后會(huì)調(diào)用該block塊
* @note completionBlock總是在主線程
*/
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* 檢查圖像是否已經(jīng)在磁盤(pán)緩存中存在(不加載圖像)
*
* @param key 保存圖像的唯一鍵
* @return 如果該圖片存在,則返回YES
*/
- (BOOL)diskImageExistsWithKey:(NSString *)key;
/**
* 獲得指定 key 對(duì)應(yīng)的緩存路徑(需要指定緩存路徑的根目錄)
*
* @param key 鍵(可以調(diào)用cacheKeyForURL方法獲得)
* @param path 緩存路徑根文件夾
*/
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
/**
* 獲得指定 key 的默認(rèn)緩存路徑
*
* @param key 鍵(可以調(diào)用cacheKeyForURL方法獲得)
*
* @return 默認(rèn)緩存路徑
*/
- (NSString *)defaultCachePathForKey:(NSString *)key;
UIImageView+WebCache方法入口
UIImageView+WebCache是SDWebImage方法的入口,其中最基本的方法是
所有設(shè)置圖片調(diào)用的方法最終都會(huì)調(diào)用這個(gè)方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
方法解析
//所有設(shè)置圖片調(diào)用的方法最終都會(huì)調(diào)用這個(gè)方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelCurrentImageLoad];//取消先前的下載任務(wù)
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//動(dòng)態(tài)添加屬性
if (!(options & SDWebImageDelayPlaceholder)) {
//如果SDWebImageDelayPlaceholder為nil
dispatch_main_async_safe(^{
self.image = placeholder;//設(shè)置占位圖
});
}
//url不為nil
if (url) {
// check if activityView is enabled or not
if ([self showActivityIndicatorView]) {
//是否顯示進(jìn)度條
[self addActivityIndicator];
}
//下載操作
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator];//移除進(jìn)度條
if (!wself) return;//self是否被釋放
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{//不要自動(dòng)設(shè)置圖片,那么調(diào)用Block傳入U(xiǎn)IImage對(duì)象
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
//設(shè)置圖片
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
//設(shè)置占位圖片
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
//調(diào)用Block
completedBlock(image, error, cacheType, url);
}
});
}];
//將生成的加載操作賦給UIView的自定義屬性
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];//給添加的屬性賦值
} else {
dispatch_main_async_safe(^{
//錯(cuò)誤處理
[self removeActivityIndicator];
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}