SDWebImage源代碼解析(三)Utils

前言

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)部封裝了SDImageCacheSDWebImageDownloader
主要包含以下幾塊:

  • 定義與初始化相關(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)系

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末业舍,一起剝皮案震驚了整個濱河市抖拦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舷暮,老刑警劉巖态罪,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異下面,居然都是意外死亡复颈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門诸狭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人君纫,你說我怎么就攤上這事驯遇。” “怎么了蓄髓?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵叉庐,是天一觀的道長。 經(jīng)常有香客問我会喝,道長陡叠,這世上最難降的妖魔是什么玩郊? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮枉阵,結(jié)果婚禮上译红,老公的妹妹穿的比我還像新娘。我一直安慰自己兴溜,他們只是感情好侦厚,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拙徽,像睡著了一般刨沦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膘怕,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天想诅,我揣著相機(jī)與錄音,去河邊找鬼岛心。 笑死来破,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹉梨。 我是一名探鬼主播讳癌,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼存皂!你這毒婦竟也來了晌坤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旦袋,失蹤者是張志新(化名)和其女友劉穎骤菠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疤孕,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡商乎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祭阀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹉戚。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖专控,靈堂內(nèi)的尸體忽然破棺而出抹凳,到底是詐尸還是另有隱情,我是刑警寧澤伦腐,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布赢底,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏幸冻。R本人自食惡果不足惜粹庞,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洽损。 院中可真熱鬧庞溜,春花似錦、人聲如沸趁啸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽不傅。三九已至旅掂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間访娶,已是汗流浹背商虐。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留崖疤,地道東北人秘车。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像劫哼,于是被迫代替她去往敵國和親叮趴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容