前言
CSDN地址:http://blog.csdn.net/game3108/article/details/52638886
本文的中文注釋代碼demo更新在我的github上。
上篇文章講解的了SDWebImage的Download部分镰禾,這篇講講一下Utils部分。
Utils
Utils主要包含以下3個類:
- SDWebImageManager
核心的下載控制類 - SDWebImageDecoder
圖片解碼類 - SDWebImagePrefetcher
圖片預(yù)下載類
以下將分別介紹3個類的源代碼。
SDWebImageDecoder
SDWebImageDecoder內(nèi)容就是UIImage的一個Category
定義如下:
@interface UIImage (ForceDecode)
//解碼圖片
+ (UIImage *)decodedImageWithImage:(UIImage *)image;
@end
實現(xiàn)如下:
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
// while downloading huge amount of images
// autorelease the bitmap context
// and all vars to help system to free memory
// when there are memory warning.
// on iOS7, do not forget to call
// [[SDImageCache sharedImageCache] clearMemory];
// 當(dāng)下載大量圖片的時候所坯,自動釋放bitmap context和所有變量去幫助節(jié)約內(nèi)存
// 在ios7上別忘記調(diào)用 [[SDImageCache sharedImageCache] clearMemory];
if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
return nil;
}
@autoreleasepool{
// do not decode animated images
// 不去解碼gif圖片
if (image.images != nil) {
return image;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
//獲取任何的alpha軌道
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
//有alpha信息的圖片晒骇,直接返回
if (anyAlpha) {
return image;
}
// current
//表示需要使用的色彩標(biāo)準(zhǔn)(為創(chuàng)建CGColor做準(zhǔn)備)
//例如RBG:CGColorSpaceCreateDeviceRGB
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
//是否不支持這些標(biāo)準(zhǔn)
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
//如果不支持說明是RGB的標(biāo)準(zhǔn)
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
}
//獲取圖片高和寬
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
//每一個pixel是4個byte
NSUInteger bytesPerPixel = 4;
//每一行的byte大小
NSUInteger bytesPerRow = bytesPerPixel * width;
//1Byte=8bit
NSUInteger bitsPerComponent = 8;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
// kCGImageAlphaNone無法使用CGBitmapContextCreate.
// 既然原始圖片這邊沒有alpha信息纳胧,就使用kCGImageAlphaNoneSkipLast
// 去創(chuàng)造沒有alpha信息的bitmap的圖片訊息
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
bitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
// Draw the image into the context and retrieve the new bitmap image without alpha
// 將圖片描繪進(jìn) 圖片上下文 去 取回新的沒有alpha的bitmap圖片
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
//適應(yīng)屏幕和方向
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
if (unsupportedColorSpace) {
CGColorSpaceRelease(colorspaceRef);
}
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
這邊解碼圖片的做法和download完成處理中的類似镰吆,兩邊可以一起驗證一下。
這邊解碼圖片主要原因就是圖片的加載是lazy加載跑慕,在真正顯示的時候才進(jìn)行加載鼎姊,這邊先解碼一次就會直接把圖片加載完成。
SDWebImageManager
SDWebImageManager是核心的下載控制類方法相赁,它內(nèi)部封裝了SDImageCache
與SDWebImageDownloader
主要包含以下幾塊:
- 定義與初始化相關(guān)
- 存儲與判斷的一些封裝方法
- 下載圖片核心方法
還有一些已經(jīng)被廢棄的實現(xiàn),為了支持上的需要慰于,也沒刪掉钮科,這里也就不多說明了
定義與初始化相關(guān)
SDWebImageManager.h中不僅包含SDWebImageManager類的聲明,還包含了相關(guān)的delegate方法@protocol SDWebImageManagerDelegate <NSObject>
還有下載設(shè)置enum與相關(guān)block的聲明婆赠。
定義如下:
//下載設(shè)置
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
//默認(rèn)绵脯,當(dāng)一個url下載失敗,這個url會到黑名單中不再嘗試下載
//這個設(shè)置禁止這個黑名單策略
SDWebImageRetryFailed = 1 << 0,
//默認(rèn)休里,圖片從ui交互的時候就開始下載蛆挫。這個設(shè)置是的下載延遲到uiscrollview放手拖動的的時候開始下載
SDWebImageLowPriority = 1 << 1,
//只存在內(nèi)存中
SDWebImageCacheMemoryOnly = 1 << 2,
//這個設(shè)置允許進(jìn)度下載。默認(rèn)情況下妙黍,圖片只在下載完成展示
SDWebImageProgressiveDownload = 1 << 3,
//即使圖片被緩存了悴侵,需要http的緩存策略控制并且在需要刷新的時候刷新
//硬盤緩存會通過NSURLCache緩存而不是SDWebImage會造成輕微的性能下降
//這個設(shè)置可以幫助處理圖片在同一個url下變化的問題。比如facebook的圖片api:profile pics
//如果一個緩存圖片已經(jīng)更新拭嫁,完成回調(diào)會調(diào)用一次完成的圖片
SDWebImageRefreshCached = 1 << 4,
//iOS4以上可免,持續(xù)下載圖片當(dāng)app進(jìn)入后臺抓于。這個是通過請求系統(tǒng)在后臺的額外時間讓請求完成
//如果后臺需要操作則會被取消
SDWebImageContinueInBackground = 1 << 5,
//處理設(shè)置在NSHTTPCookieStore的cookie
SDWebImageHandleCookies = 1 << 6,
//允許訪問不信任的SSL證書
//用來測試的目的,小心在生產(chǎn)環(huán)境中使用
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//默認(rèn)浇借,圖片會按照順序下載捉撮,這個會提高它的順序
SDWebImageHighPriority = 1 << 8,
//默認(rèn)占位圖片會在圖片加載過程中使用,這個設(shè)置可以延遲加載占位圖片直到圖片加載完成之后
SDWebImageDelayPlaceholder = 1 << 9,
//我們不會去調(diào)transformDownloadedImage delegate方法在動畫圖片上妇垢,因為這個會撕裂圖片
//用這個設(shè)置去調(diào)用它
SDWebImageTransformAnimatedImage = 1 << 10,
//默認(rèn)巾遭,圖片會在圖片下載完成后加到imageview上。單在有些情況闯估,我們想設(shè)置圖片前進(jìn)行一些設(shè)置(比如加上篩選或者淡入淡出的動畫)
//用這個設(shè)置去手動在下載完成后設(shè)置
SDWebImageAvoidAutoSetImage = 1 << 11
};
/**
下載完成block
@param image 圖片
@param error 錯誤
@param cacheType 下載類型
@param imageURL 圖片url
*/
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
/**
下載完成block
@param image 圖片
@param error 錯誤
@param cacheType 下載類型
@param finished 是否完成
@param imageURL 圖片url
*/
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
/**
篩選緩存key的block
@param url 圖片url
@return 篩選的key
*/
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
@class SDWebImageManager;
@protocol SDWebImageManagerDelegate <NSObject>
@optional
/**
控制那個圖片在沒有緩存的時候下載
@param imageManager 當(dāng)前的`SDWebImageMananger`
@param imageURL 圖片下載url
@return 返回NO去防止下載圖片在cache上沒有找到灼舍。沒有實現(xiàn)默認(rèn)YES
*/
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**
允許去在圖片下載完成后,先于緩存到硬盤和內(nèi)存前進(jìn)行轉(zhuǎn)換
注意:這個方法在一個global線程上被調(diào)用睬愤,為了不阻塞主線程
@param imageManager 當(dāng)前的`SDWebImageManager`
@param image 需要變化的圖片
@param imageURL 圖片的url
@return 已經(jīng)變化了的圖片
*/
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end
@interface SDWebImageManager : NSObject
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
//緩存片仿,這邊只讀,在.m的extension中尤辱,可以重寫為readwrite
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//下載器
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
/**
這個緩存篩選是在每次SDWebImageManager需要變化一個url到一個緩存key的時候調(diào)用砂豌,可以用來移除動態(tài)的image圖片
下面的例子設(shè)置了一個篩選器在application delegate可以移除任何的查詢字符串從url,在使用它當(dāng)做緩存key之前
* @code
[[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
}];
* @endcode
*/
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
在SDWebImageManager.m中光督,定義了一個NSOperation的封裝SDWebImageCombinedOperation
阳距,以及失敗url的存儲和正在運行operation的存儲。
實現(xiàn)如下:
//nsoperation的一層封裝,實現(xiàn)SDWebImageOperation協(xié)議
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
@interface SDWebImageManager ()
//重新结借,可以在內(nèi)部讀與寫
@property (strong, nonatomic, readwrite) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;
//失敗url的存儲
@property (strong, nonatomic) NSMutableSet *failedURLs;
//正在運行的operation組合
@property (strong, nonatomic) NSMutableArray *runningOperations;
+ (id)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
//初始化默認(rèn)的cache和downloader
- (instancetype)init {
SDImageCache *cache = [SDImageCache sharedImageCache];
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
return [self initWithCache:cache downloader:downloader];
}
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
@end
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
//檢查是否操作已經(jīng)cancel了筐摘,才能去調(diào)用cancelblock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
//別忘記設(shè)置為nil,否則會crash
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
//設(shè)置取消標(biāo)志
self.cancelled = YES;
//取消存儲的operation草走
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
//調(diào)用cancelblock
if (self.cancelBlock) {
self.cancelBlock();
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}
@end
存儲與判斷的一些封裝方法
存儲與判斷的封裝就是一些SDImageCache
的一些查詢方法的封裝與取消等操作的方法船老。
相關(guān)方法聲明如下:
/**
通過url保存圖片
@param image 需要緩存的圖片
@param url 圖片url
*/
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
/**
取消所有操作
*/
- (void)cancelAll;
//判斷是否有操作還在運行
- (BOOL)isRunning;
/**
判斷是否有圖片已經(jīng)被緩存
@param url 圖片url
@return 是否圖片被緩存
*/
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
/**
判斷是否圖片只在在硬盤緩存
@param url 圖片url
@return 是否圖片只在硬盤緩存
*/
- (BOOL)diskImageExistsForURL:(NSURL *)url;
/**
異步判斷是否圖片已經(jīng)被緩存
@param url 圖片url
@param completionBlock 完成block
@note 完成block一直在主線程調(diào)用
*/
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
異步判斷是否只在硬盤緩存
@param url 圖片url
@param completionBlock 完成block
*/
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回一個url的緩存key
- (NSString *)cacheKeyForURL:(NSURL *)url;
方法實現(xiàn)如下:
- (NSString *)cacheKeyForURL:(NSURL *)url {
if (!url) {
return @"";
}
//有filter走filter
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
} else {
//默認(rèn)是url的完全string
return [url absoluteString];
}
}
- (BOOL)cachedImageExistsForURL:(NSURL *)url {
//獲得url的cache key
NSString *key = [self cacheKeyForURL:url];
//如果在memory中咖熟,直接返回yes
if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES;
//否則返回是否在disk中的結(jié)果
return [self.imageCache diskImageExistsWithKey:key];
}
- (BOOL)diskImageExistsForURL:(NSURL *)url {
//獲得url的cache key
NSString *key = [self cacheKeyForURL:url];
//返回disk中的結(jié)果
return [self.imageCache diskImageExistsWithKey:key];
}
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
//獲得url的cache key
NSString *key = [self cacheKeyForURL:url];
//判斷是否在memory中
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
//在memory中直接回調(diào)block
if (isInMemoryCache) {
// making sure we call the completion block on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(YES);
}
});
return;
}
//否則調(diào)用disk的查詢和回調(diào)
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
//直接調(diào)用disk的查詢和回調(diào)
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url {
if (image && url) {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache storeImage:image forKey:key toDisk:YES];
}
}
- (void)cancelAll {
//獲取所有存儲的正在運行中的operation,然后全部取消并刪除
@synchronized (self.runningOperations) {
NSArray *copiedOperations = [self.runningOperations copy];
[copiedOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeObjectsInArray:copiedOperations];
}
}
- (BOOL)isRunning {
//判斷是否有正在運行的operation
BOOL isRunning = NO;
@synchronized(self.runningOperations) {
isRunning = (self.runningOperations.count > 0);
}
return isRunning;
}
下載圖片核心方法
下載圖片的方法定義如下:
/**
通過url下載圖片如果緩存不存在
@param url 圖片url
@param options 下載設(shè)置
@param progressBlock 進(jìn)度block
@param completedBlock 完成block
@return 返回SDWebImageDownloaderOperation的實例
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
實現(xiàn)如下:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
//沒有完成block柳畔,則報錯馍管,應(yīng)該用`[SDWebImagePrefetcher prefetchURLs]`替代
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
//很常見的錯誤是把nsstring對象代替nsurl對象傳過來。單因為一些奇怪的原因薪韩,xcode不會在這種錯誤上報warning确沸,所以這里我們只能保護(hù)一下這個錯誤允許url當(dāng)nsstring傳過來
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
//防止app的崩潰因為參數(shù)類型錯誤,比如傳輸NSNull
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//創(chuàng)建operation的封裝對象
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
//判斷是否是已經(jīng)失敗過的url
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
//如果url長度為0或者說設(shè)置不允許失敗過的url重試俘陷,則調(diào)用完成block返回錯誤
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;
}
//將operation存儲
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//獲得url的key
NSString *key = [self cacheKeyForURL:url];
//設(shè)置nsoperation為先從cache中異步獲取
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
//如果operation已經(jīng)被取消罗捎,則從存儲中刪除直接返回
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
//(沒有圖片 || 需要更新緩存) && (不存在`imageManager:shouldDownloadImageForURL:`方法) || (`imageManager:shouldDownloadImageForURL:`返回YES)
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果有圖片同時需要更新緩存,先返回一次之前緩存的圖片
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
//如果image在cache中,但是SDWebImageRefreshCached被設(shè)置拉盾。需要注意緩存的圖片需要重新下載去似的NSURLCache去從服務(wù)器更新它們
completedBlock(image, nil, cacheType, YES, url);
});
}
// download if no image or requested to refresh anyway, and download allowed by delegate
//在沒有圖片或者一定要下載的時候下載圖片
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 (image && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
//強(qiáng)制關(guān)閉進(jìn)度展示
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
//忽略NSURLCache的緩存
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//構(gòu)造下載operation去下載圖片
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
//如果取消更新桨菜,這邊不需要去調(diào)用complete block,因為上邊已經(jīng)調(diào)過了
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
}
//如果存在錯誤
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
//如果error不是以下錯誤捉偏,則將url存入失敗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];
}
}
}
else {
//如果允許重新下載雷激,則將url從失敗url列表中刪除
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否需要在硬盤緩存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//圖片更新?lián)糁辛薔SURLCache的緩存替蔬,不用掉完成block
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
}
//如果有下載圖片,且不是gif和需要轉(zhuǎn)化圖片
else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
//子線程轉(zhuǎn)化圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
//如果轉(zhuǎn)化圖片存在并且結(jié)束了
if (transformedImage && finished) {
//是否圖片被轉(zhuǎn)換了
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//將圖片存入cache中
[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);
}
});
});
}
else {
//如果下載圖片并且完成了
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);
}
});
}
}
//如果結(jié)束了,將operation從存儲中刪除
if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
//設(shè)置的取消block
operation.cancelBlock = ^{
//下載取消
[subOperation cancel];
//并且從operation中刪除
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
//如果有圖片
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
else {
// Image not in cache and download disallowed by delegate
//圖片在cache中不存在屎暇,并且下載不允許
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}];
return operation;
}
這里建造了SDWebImageCombinedOperation
的對象承桥,去存儲SDImageCache
查詢圖片緩存的operation,然后再查詢緩存的block中根悼,嵌套了一個SDWebImageDownloader
下載圖片的subOperation凶异。在SDWebImageCombinedOperation
的cancelBlock中去設(shè)置了subOperation的取消操作。
SDWebImagePrefetcher
SDWebImagePrefetcher方法主要是用于部分圖片需要先行下載并存儲的情況挤巡。
主要設(shè)計了兩種回調(diào)方式
- 1.SDWebImagePrefetcherDelegate
用來處理每一個預(yù)下載完成的回調(diào)剩彬,以及所有下載完成的回調(diào) - 2.block
用來處理整體進(jìn)度的回調(diào),返回的是下載完成的數(shù)量和總數(shù)量等
相關(guān)實現(xiàn)如下:
@protocol SDWebImagePrefetcherDelegate <NSObject>
@optional
/**
當(dāng)一張圖片預(yù)加載的時候調(diào)用
@param imagePrefetcher 當(dāng)前圖片的預(yù)加載類
@param imageURL 圖片url
@param finishedCount 已經(jīng)預(yù)加載的數(shù)量
@param totalCount 總共預(yù)加載的圖片數(shù)量
*/
- (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount;
/**
當(dāng)所有圖片被預(yù)加載時候調(diào)用
@param imagePrefetcher 當(dāng)前圖片的預(yù)加載類
@param totalCount 總共預(yù)加載的圖片數(shù)量
@param skippedCount 跳過的數(shù)量
*/
- (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount;
@end
/**
預(yù)加載進(jìn)度block
@param noOfFinishedUrls 已經(jīng)完成的數(shù)量矿卑,無論成功失敗
@param noOfTotalUrls 總數(shù)量
*/
typedef void(^SDWebImagePrefetcherProgressBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls);
/**
預(yù)加載完成block
@param noOfFinishedUrls 已經(jīng)完成的數(shù)量喉恋,無論成功失敗
@param noOfSkippedUrls 跳過的數(shù)量
*/
typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls);
SDWebImagePrefetcher的主要加載方法如下:
//開始加載
- (void)startPrefetchingAtIndex:(NSUInteger)index {
if (index >= self.prefetchURLs.count) return;
//已經(jīng)請求數(shù)量+1
self.requestedCount++;
[self.manager downloadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!finished) return;
//完成的請求數(shù)量+1
self.finishedCount++;
if (image) {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,[self.prefetchURLs count]);
}
}
else {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,[self.prefetchURLs count]);
}
// Add last failed
//失敗的請求數(shù)量—+1
self.skippedCount++;
}
//回調(diào)下載的過程中的回調(diào)
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
[self.delegate imagePrefetcher:self
didPrefetchURL:self.prefetchURLs[index]
finishedCount:self.finishedCount
totalCount:self.prefetchURLs.count
];
}
//總數(shù)>請求數(shù),說明還沒請求完
if (self.prefetchURLs.count > self.requestedCount) {
dispatch_async(self.prefetcherQueue, ^{
[self startPrefetchingAtIndex:self.requestedCount];
});
//全部請求完成
} else if (self.finishedCount == self.requestedCount) {
//完成回調(diào)
[self reportStatus];
if (self.completionBlock) {
self.completionBlock(self.finishedCount, self.skippedCount);
self.completionBlock = nil;
}
self.progressBlock = nil;
}
}];
}
//完成狀態(tài)回調(diào)
- (void)reportStatus {
//獲得所有加載數(shù)量母廷,回調(diào)delegate
NSUInteger total = [self.prefetchURLs count];
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
[self.delegate imagePrefetcher:self
didFinishWithTotalCount:(total - self.skippedCount)
skippedCount:self.skippedCount
];
}
}
- (void)prefetchURLs:(NSArray *)urls {
[self prefetchURLs:urls progress:nil completed:nil];
}
/**
列所有需要預(yù)加載的url
同時1張圖圖片只會下載1次
跳過下載失敗的圖片并列島下一個列表中
@param urls 需要預(yù)加載的url列表
@param progressBlock 進(jìn)度block
@param completionBlock 完成block
*/
- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock {
//防止重復(fù)的預(yù)加載請求
[self cancelPrefetching]; // Prevent duplicate prefetch request
self.startedTime = CFAbsoluteTimeGetCurrent();
self.prefetchURLs = urls;
self.completionBlock = completionBlock;
self.progressBlock = progressBlock;
if (urls.count == 0) {
if (completionBlock) {
completionBlock(0,0);
}
} else {
// Starts prefetching from the very first image on the list with the max allowed concurrency
NSUInteger listCount = self.prefetchURLs.count;
for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
[self startPrefetchingAtIndex:i];
}
}
}
- (void)cancelPrefetching {
self.prefetchURLs = nil;
self.skippedCount = 0;
self.requestedCount = 0;
self.finishedCount = 0;
[self.manager cancelAll];
}
總結(jié)
Utils可以算是集合了前兩章總結(jié)的Cache與Downloader的總和轻黑。提供了對此兩種基礎(chǔ)方法的一層封裝,提供給外部使用琴昆。
參考資料
1.SDWebImage源碼淺析
2.UIColor氓鄙,CGColor,CIColor三者的區(qū)別和聯(lián)系