SDWebImage學(xué)習(xí)筆記(六):SDWebImageManager + SDWebImagePrefetcher

SDWebImageManager主要關(guān)聯(lián)SDWebImageDownloader和SDImageCache操作,即異步下載圖片后進(jìn)行緩存處理。

SDWebImageOptions說明:

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #1d9421}p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; min-height: 16.0px}p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #c91b13}p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo}p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px 'PingFang SC'; color: #1d9421}p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #c32275}span.s1 {font-variant-ligatures: no-common-ligatures}span.s2 {font-variant-ligatures: no-common-ligatures; color: #822e0e}span.s3 {font-variant-ligatures: no-common-ligatures; color: #c32275}span.s4 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures}span.s5 {font-variant-ligatures: no-common-ligatures; color: #000000}span.s6 {font: 14.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures}span.s7 {font-variant-ligatures: no-common-ligatures; color: #c91b13}span.s8 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures; color: #000000}span.s9 {font-variant-ligatures: no-common-ligatures; color: #0435ff}span.s10 {font-variant-ligatures: no-common-ligatures; color: #1d9421}span.s11 {font: 14.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures; color: #1d9421}

/*
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey <rs@dailymotion.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

#import "SDWebImageManager.h"
#import <objc/message.h>

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>

// 是否取消當(dāng)前所有操作
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
// 沒有參數(shù)取消回調(diào)
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
// 執(zhí)行緩存的操作
@property (strong, nonatomic) NSOperation *cacheOperation;

@end

@interface SDWebImageManager ()

// 圖片緩存對(duì)象
@property (strong, nonatomic, readwrite) SDImageCache *imageCache;
// 圖片下載對(duì)象
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;
// 下載失敗的圖片URL
@property (strong, nonatomic) NSMutableSet *failedURLs;
// 裝載正在執(zhí)行操作的數(shù)組
@property (strong, nonatomic) NSMutableArray *runningOperations;

@end

@implementation SDWebImageManager

/**
 *  生成一個(gè)SDWebImagemanager的單例
 *
 *  @return <#return value description#>
 */
+ (id)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

/**
 *  初始化SDImageCache杖玲、SDWebImageDownloader
 *
 *  @return <#return value description#>
 */
- (instancetype)init {
    SDImageCache *cache = [SDImageCache sharedImageCache];
    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    return [self initWithCache:cache downloader:downloader];
}

/**
 *  初始化SDImageCache、SDWebImageDownloader
 *
 *  @return <#return value description#>
 */
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}

/**
 *  根絕URL獲取緩存中的key
 *
 *  @param url 圖片URL
 *
 *  @return 圖片在緩存中對(duì)應(yīng)的Key
 */
- (NSString *)cacheKeyForURL:(NSURL *)url {
    if (!url) {
        return @"";
    }
    
    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    } else {
        return [url absoluteString];
    }
}

/**
 *  根據(jù)URL判斷緩存中是否存在圖片:先判斷內(nèi)存緩存铁追、在判斷磁盤緩存
 *
 *  @param url 圖片URL
 *
 *  @return 是否存在圖片
 */
- (BOOL)cachedImageExistsForURL:(NSURL *)url {
    NSString *key = [self cacheKeyForURL:url];
    if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES;
    return [self.imageCache diskImageExistsWithKey:key];
}

/**
 *  根據(jù)URL判斷磁盤緩存中是否存在圖片
 *
 *  @param url 圖片URL
 *
 *  @return 是否存在圖片
 */
- (BOOL)diskImageExistsForURL:(NSURL *)url {
    NSString *key = [self cacheKeyForURL:url];
    return [self.imageCache diskImageExistsWithKey:key];
}

/**
 *  根據(jù)URL判斷緩存中是否存在圖片并進(jìn)行回調(diào):先判斷內(nèi)存緩存姻成、在判斷磁盤緩存
 *
 *  @param url             圖片URL
 *  @param completionBlock 圖片在緩存中是否存在回調(diào)
 */
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    
    BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
    
    if (isInMemoryCache) {
        // 確保回調(diào)在主線程中執(zhí)行:making sure we call the completion block on the main queue
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completionBlock) {
                completionBlock(YES);
            }
        });
        return;
    }
    
    // 該方法的回調(diào)已在主線程中執(zhí)行
    [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);
        }
    }];
}

/**
 *  根據(jù)URL判斷磁盤緩存中是否存在圖片并進(jìn)行回調(diào)
 *
 *  @param url             圖片URL
 *  @param completionBlock 圖片在緩存中是否存在回調(diào)
 */
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    
    [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);
        }
    }];
}

/**
 *  根據(jù)URL下載圖片
 *
 *  @param url            圖片URL
 *  @param options        下載圖片選項(xiàng):SDWebImageOptions
 *  @param progressBlock  圖片下載進(jìn)度回調(diào)
 *  @param completedBlock 圖片下載完成回調(diào)
 *
 *  @return SDWebImageOperation
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    // completedBlock為空時(shí)昙楚,斷言將被執(zhí)行近速,程序Crash,并打印出斷言中的描述信息堪旧。Invoking this method without a completedBlock is pointless
    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.
    // 確保傳進(jìn)來的URL類型為NSURL削葱,如果為NSString則進(jìn)行類型轉(zhuǎn)化
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    // 防止URL為空時(shí)程序崩潰
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    // 執(zhí)行圖片下載操作的集合
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    // 圖片URL是否失敗
    BOOL isFailedUrl = NO;
    // 創(chuàng)建一個(gè)互斥鎖,保證此時(shí)沒有其它線程對(duì)self對(duì)象進(jìn)行修改
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }

    // URL長度為0淳梦,或 獲取該URL已下載失敗過且操作類型沒有禁用掉黑名單列表析砸,則該圖片就下載失敗,返回失敗block
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        // 在主線程中執(zhí)行回調(diào)
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }

    // 創(chuàng)建一個(gè)互斥鎖谭跨,保證此時(shí)沒有其它線程對(duì)self對(duì)象進(jìn)行修改
    @synchronized (self.runningOperations) {
        // 將操作加入到當(dāng)前運(yùn)行操作的數(shù)組中
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];

    // 根據(jù)key從緩存里面查找
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        // 操作被取消干厚,則從當(dāng)前運(yùn)行操作的數(shù)組中刪除該操作
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }

            return;
        }

        // 沒有圖片或操作類型會(huì)刷新緩存 且 delegate允許下載圖片
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            // // 有圖片 并且操作類型為 SDWebImageRefreshCached(先用緩存圖片回調(diào)block李滴,圖片下載完成后再回調(diào)block)
            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.
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }

            // download if no image or requested to refresh anyway, and download allowed by delegate
            // 如果緩存沒有圖片或請(qǐng)求刷新,并通過委托下載圖片蛮瞄,則下載圖片
            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
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            // 下載圖片
            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;
                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
                    // 操作取消則不做任何處理
                    // 如果這里進(jìn)行了completedBlock回調(diào)所坯,由于對(duì)同一個(gè)對(duì)象是競(jìng)爭(zhēng)條件,可能會(huì)覆蓋新數(shù)據(jù)挂捅?芹助??闲先?状土?
                }
                else if (error) {
                    // 出錯(cuò)
                    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) {
                        // 下載失敗圖片的URL添加到失敗URL數(shù)組
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                // 下載成功
                else {
                    //
                    if ((options & SDWebImageRetryFailed)) {
                        // 操作類型為失敗重新刷新下載,失敗圖片的URL添加到失敗URL數(shù)組
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    // 是否存儲(chǔ)在磁盤
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    }
                    // 圖片下載成功且需要轉(zhuǎn)換圖片
                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        // 在全局隊(duì)列中異步進(jìn)行操作
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            // 根據(jù)代理獲取轉(zhuǎn)換后的圖片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            // 轉(zhuǎn)換圖片存在 并且下載圖片操作已完成 則存儲(chǔ)圖片
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }

                            // completedBlock回調(diào)
                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }
                    // 存儲(chǔ)圖片并completedBlock回調(diào)
                    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);
                            }
                        });
                    }
                }

                // 圖片下載完成伺糠,則在運(yùn)行的操作數(shù)組中刪除該操作
                if (finished) {
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }
            }];
            // 取消操作,從數(shù)組中刪除該操作
            operation.cancelBlock = ^{
                [subOperation cancel];
                
                @synchronized (self.runningOperations) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
            };
        }
        // 有圖片且線程沒有被取消,則返回有圖片的completedBlock
        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
            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;
}

/**
 *  將圖片存入緩存
 *
 *  @param image 圖片
 *  @param url   圖片URL
 */
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url {
    if (image && url) {
        NSString *key = [self cacheKeyForURL:url];
        [self.imageCache storeImage:image forKey:key toDisk:YES];
    }
}

/**
 *  取消掉當(dāng)前所有的下載圖片的operation
 */
- (void)cancelAll {
    @synchronized (self.runningOperations) {
        NSArray *copiedOperations = [self.runningOperations copy];
        [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
        [self.runningOperations removeObjectsInArray:copiedOperations];
    }
}

/**
 *  檢查是否有一個(gè)或者多個(gè)operation正在執(zhí)行(簡單來說就是check是否有圖片在下載)
 *
 *  @return <#return value description#>
 */
- (BOOL)isRunning {
    BOOL isRunning = NO;
    @synchronized(self.runningOperations) {
        isRunning = (self.runningOperations.count > 0);
    }
    return isRunning;
}

@end

@implementation SDWebImageCombinedOperation

- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    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

@implementation SDWebImageManager (Deprecated)

// 廢棄的方法:deprecated method, uses the non deprecated method
// adapter for the completion block
- (id <SDWebImageOperation>)downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock {
    return [self downloadImageWithURL:url
                              options:options
                             progress:progressBlock
                            completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                                if (completedBlock) {
                                    completedBlock(image, error, cacheType, finished);
                                }
                            }];
}

@end

SDWebImagePrefecher注釋:

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #1d9421}p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; min-height: 16.0px}p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #c91b13}p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo}p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px 'PingFang SC'; color: #1d9421}p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #c32275}span.s1 {font-variant-ligatures: no-common-ligatures}span.s2 {font-variant-ligatures: no-common-ligatures; color: #822e0e}span.s3 {font-variant-ligatures: no-common-ligatures; color: #c32275}span.s4 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures}span.s5 {font: 14.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures}span.s6 {font-variant-ligatures: no-common-ligatures; color: #0435ff}span.s7 {font-variant-ligatures: no-common-ligatures; color: #000000}span.s8 {font-variant-ligatures: no-common-ligatures; color: #1d9421}span.s9 {font: 14.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures; color: #1d9421}span.s10 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures; color: #000000}

/*
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey <rs@dailymotion.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

#import "SDWebImagePrefetcher.h"

@interface SDWebImagePrefetcher ()

@property (strong, nonatomic) SDWebImageManager *manager;
// 預(yù)取圖片地址數(shù)組
@property (strong, nonatomic) NSArray *prefetchURLs;
// 請(qǐng)求數(shù)
@property (assign, nonatomic) NSUInteger requestedCount;
@property (assign, nonatomic) NSUInteger skippedCount;
// 完成數(shù)
@property (assign, nonatomic) NSUInteger finishedCount;
@property (assign, nonatomic) NSTimeInterval startedTime;
@property (copy, nonatomic) SDWebImagePrefetcherCompletionBlock completionBlock;
@property (copy, nonatomic) SDWebImagePrefetcherProgressBlock progressBlock;

@end

@implementation SDWebImagePrefetcher

+ (SDWebImagePrefetcher *)sharedImagePrefetcher {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (id)init {
    return [self initWithImageManager:[SDWebImageManager new]];
}

- (id)initWithImageManager:(SDWebImageManager *)manager {
    if ((self = [super init])) {
        _manager = manager;
        _options = SDWebImageLowPriority;
        _prefetcherQueue = dispatch_get_main_queue();
        self.maxConcurrentDownloads = 3;
    }
    return self;
}

- (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads {
    self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads;
}

- (NSUInteger)maxConcurrentDownloads {
    return self.manager.imageDownloader.maxConcurrentDownloads;
}

/**
 *  開始預(yù)取第幾張圖片
 *
 *  @param index 圖片位置
 */
- (void)startPrefetchingAtIndex:(NSUInteger)index {
    if (index >= self.prefetchURLs.count) return;
    
    // 請(qǐ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]);
            }
            // 圖片沒有下載完蒙谓,跳過數(shù)+1:Add last failed
            self.skippedCount++;
        }
        if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
            [self.delegate imagePrefetcher:self
                            didPrefetchURL:self.prefetchURLs[index]
                             finishedCount:self.finishedCount
                                totalCount:self.prefetchURLs.count
             ];
        }
        if (self.prefetchURLs.count > self.requestedCount) {
            // 預(yù)取圖片數(shù) > 請(qǐng)求數(shù),繼續(xù)預(yù)取圖片
            dispatch_async(self.prefetcherQueue, ^{
                [self startPrefetchingAtIndex:self.requestedCount];
            });
        } else if (self.finishedCount == self.requestedCount) {
            // 完成數(shù) = 請(qǐng)求數(shù)训桶,該圖片下載完成累驮,進(jìn)行回調(diào)
            [self reportStatus]; // 報(bào)告圖片完成狀態(tài)
            if (self.completionBlock) {
                self.completionBlock(self.finishedCount, self.skippedCount);
                self.completionBlock = nil;
            }
            self.progressBlock = nil;
        }
    }];
}

/**
 *  報(bào)告預(yù)取圖片完成狀態(tài)
 */
- (void)reportStatus {
    NSUInteger total = [self.prefetchURLs count];
    if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
        [self.delegate imagePrefetcher:self
               didFinishWithTotalCount:(total - self.skippedCount)
                          skippedCount:self.skippedCount
         ];
    }
}

/**
 *  根據(jù)圖片地址數(shù)組預(yù)取圖片
 *
 *  @param urls 圖片地址數(shù)組
 */
- (void)prefetchURLs:(NSArray *)urls {
    [self prefetchURLs:urls progress:nil completed:nil];
}

- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock {
    [self cancelPrefetching]; // 先取消圖片預(yù)取,防止重復(fù)操作: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 {
        // 循環(huán)預(yù)取圖片: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];
        }
    }
}

/**
 *  取消圖片預(yù)取
 */
- (void)cancelPrefetching {
    self.prefetchURLs = nil;
    self.skippedCount = 0;
    self.requestedCount = 0;
    self.finishedCount = 0;
    [self.manager cancelAll];
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舵揭,一起剝皮案震驚了整個(gè)濱河市谤专,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌午绳,老刑警劉巖置侍,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拦焚,居然都是意外死亡蜡坊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耕漱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來算色,“玉大人,你說我怎么就攤上這事螟够≡置危” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵妓笙,是天一觀的道長若河。 經(jīng)常有香客問我,道長寞宫,這世上最難降的妖魔是什么萧福? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮辈赋,結(jié)果婚禮上鲫忍,老公的妹妹穿的比我還像新娘膏燕。我一直安慰自己,他們只是感情好悟民,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布坝辫。 她就那樣靜靜地躺著,像睡著了一般射亏。 火紅的嫁衣襯著肌膚如雪近忙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天智润,我揣著相機(jī)與錄音及舍,去河邊找鬼。 笑死窟绷,一個(gè)胖子當(dāng)著我的面吹牛锯玛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钾麸,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼更振,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了饭尝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤献宫,失蹤者是張志新(化名)和其女友劉穎钥平,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姊途,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涉瘾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捷兰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片立叛。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贡茅,靈堂內(nèi)的尸體忽然破棺而出秘蛇,到底是詐尸還是另有隱情,我是刑警寧澤顶考,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布赁还,位于F島的核電站,受9級(jí)特大地震影響驹沿,放射性物質(zhì)發(fā)生泄漏艘策。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一渊季、第九天 我趴在偏房一處隱蔽的房頂上張望朋蔫。 院中可真熱鬧罚渐,春花似錦、人聲如沸驯妄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽富玷。三九已至璧坟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赎懦,已是汗流浹背雀鹃。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留励两,地道東北人导帝。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像复颈,于是被迫代替她去往敵國和親脉执。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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