SDWebImage源码详解 - SDWebImage管理器SDWebImageManager

SDWebImage源码详解 - SDWebImage管理器SDWebImageManager

前面两节,我们介绍了SDWebImage的缓存SDImageCache和下载SDWebImageDownloader,这一节我们来介绍一下SDWebImageManager,这个类是主要用来管理缓存和下载对象。在平常的使用当中,我们不直接使用SDWebImageDownloader类及SDImageCache类来执行图片的下载及缓存。而是通过这个SDWebImageManager类来调用他们,实现一个管理器的作用,是一个单例类。UIImageView+WebCache这个category背后执行操作的就是这个SDWebImageManager.

首先我们来看一下SDWebImageManager.h头文件的实现。

//定义一些操作选项,里面有些操作选项是和SDWebImageDownloaderOptions中的选项对应的
//在-(id)downloadImageWithURL:options:progress:completed:函数中我们可以看到这些选项是如何和SDWebImageDownloaderOptions中的选项对应起来的
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    //默认情况下,当URL下载失败时,URL会被列入黑名单,下次便不会再去重试,该标记用于禁用黑名单
    SDWebImageRetryFailed = 1 << 0,

    // 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时
    SDWebImageLowPriority = 1 << 1,

    // 该标记禁用磁盘缓存
    SDWebImageCacheMemoryOnly = 1 << 2,

    // 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。
    // 默认情况下,图像在下载完成后一次性显示
    SDWebImageProgressiveDownload = 1 << 3,

    // 即使图片缓存了,也期望HTTP响应cache control,并在需要的情况下从远程刷新图片。
    // 磁盘缓存将被NSURLCache处理而不是SDWebImage,因为SDWebImage会导致轻微的性能下载。
    // 该标记帮助处理在相同请求URL后面改变的图片。如果缓存图片被刷新,则完成block会使用缓存图片调用一次
    // 然后再用最终图片调用一次
    SDWebImageRefreshCached = 1 << 4,

    // 在iOS 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成
    // 如果后台任务超时,则操作被取消
    SDWebImageContinueInBackground = 1 << 5,

    // 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES
    // 来处理存储在NSHTTPCookieStore中的cookie
    SDWebImageHandleCookies = 1 << 6,

    // 允许不受信任的SSL认证
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    // 默认情况下,图片下载按入队的顺序来执行。该标记将其移到队列的前面,
    // 以便图片能立即下载而不是等到当前队列被加载
    SDWebImageHighPriority = 1 << 8,
    
    // 默认情况下,占位图片在加载图片的同时被加载。该标记延迟占位图片的加载直到图片已以被加载完成
    SDWebImageDelayPlaceholder = 1 << 9,

    // 通常我们不调用动画图片的transformDownloadedImage代理方法,因为大多数转换代码可以管理它。
    // 使用这个票房则不任何情况下都进行转换。
    SDWebImageTransformAnimatedImage = 1 << 10,
    

     // 通常情况下,当图片下载完成后就会被加载置iamgeView显示。但是有些情况下,我们想在图片被显示之前做一些处理(应用一个过滤器或者添加一个动画)。
     //当你想手动的在图片下载完成后处理图片,就设置这个标记
    SDWebImageAvoidAutoSetImage = 1 << 11
};

//声明一些回调
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);

typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);

typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

//定义一个协议SDWebImageManagerDelegate
//这个协议主要实现一些在下载过程中对图片的操作
@protocol SDWebImageManagerDelegate 

@optional

// 控制当图片在缓存中没有找到时,应该下载哪个图片,可以选择no,那么sdwebimage在缓存中没有找到这张图片的时候不会选择下载
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;

// 允许在图片已经被下载完成且被缓存到磁盘或内存前立即转换。异步执行
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;

@end

@interface SDWebImageManager : NSObject

//协议对象属性
@property (weak, nonatomic) id  delegate;

//缓存对象
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//下载器对象
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;


//这个cacheKeyFilter是一个block.这个block的作用就是生成一个image的key.
//因为sdwebimage的缓存原理你可以当成是一个字典,每一个字典的value就是一张image,
//cacheKeyFilter根据某个规则对这个图片的url做一些操作生成的
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;


//单例函数
+ (SDWebImageManager *)sharedManager;

//这个就是SDWebImageManager类的核心所在  
//第一个参数是必须要的,就是image的url
//第二个参数就是我们上面的Options,你可以定制化各种各样的操作.详情参上. 
// 第三个参数是一个回调block,用于图片在下载过程中的回调.
// 第四个参数是一个下载完成的回调.会在图片下载完成后回调.
// 返回值是一个NSObject类,并且这个NSObject类是conforming一个协议这个协议叫做SDWebImageOperation,这个协议很简单,就是一个cancel掉operation的协议.

- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;


//将图片存入cache的方法,键为url
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;

//取消掉当前所有的下载图片的操作operation
- (void)cancelAll;

//查看是否还有下载操作在执行
- (BOOL)isRunning;

//通过一个image的url检查缓存中是否已经存在这个图片,如果存在返回yes,否则返回no
- (BOOL)cachedImageExistsForURL:(NSURL *)url;

//检测一个image是否已经被缓存到磁盘(是否存且仅存在disk里)
- (BOOL)diskImageExistsForURL:(NSURL *)url;


//如果检测到图片已经被缓存,那么执行回调block.
//这个block会永远执行在主线程.也就是你可以在这个回调block里更新ui
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;

//如果检测到图片已经被缓存在磁盘(存且仅存在disk),那么执行回调block.
//这个block会永远执行在主线程.也就是你可以在这个回调block里更新ui
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;


//通过image的url返回image存在缓存里的key.
- (NSString *)cacheKeyForURL:(NSURL *)url;

@end

可以看出SDWebImageManager主要是绑定一个imageCache和一个下载器,通过一些属性和对外的函数接口来实现对imageCache和下载器的操作。这个类的核心在于这个函数:-(id)downloadImageWithURL:options:progress:completed:,这个函数的具体实现:

- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    // 如果没有完成回调函数,则提示If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    //判断url的合法性
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    
    //查看url是否是之前下载失败过的
    BOOL isFailedUrl = NO;
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
    //如果url为nil,或者在不可重试的情况下是一个下载失败过的url,则直接返回操作对象并调用完成回调
    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];
    }
    NSString *key = [self cacheKeyForURL:url];
    
    //cacheOperation是一个用来下载图片并且缓存的operation
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        //判断operation这时候有没有执行cancel操作,
        //如果cancel掉了就把这个operation从我们的正在执行的operation数组里remove掉然后return
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }

            return;
        }
        
        //如果图片在缓存中不存在或者满足一些设置,则创建一个下载器去下载图片
        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.
                    //如果在缓存中找到图片,但是SDWebImageRefreshCached置位,
                    //则尝试重新下载,让NSURLCache有机会得到刷新
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }

            //开始下载
            //下面都是判断我们的options里包含哪些SDWebImageOptions,
            //然后给我们的downloaderOptions相应的添加对应的SDWebImageDownloaderOptions
            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;
            }
            
            // 调用imageDownloader去下载image并且返回执行这个request的download的operation
            id  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) {
                    // 操作被取消,则不做任务事情
                }
                // 如果出错,则调用完成回调,并将url放入下载失败url数组中
                else if (error) {
                    dispatch_main_sync_safe(^{
                        if (strongOperation && !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                    });

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //如果设置了SDWebImageRetryFailed则将下载失败的url从failedURLs数组中删除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    }
                    // 在全局队列中并行处理图片的缓存
                    // 首先对图片做个转换操作,该操作是代理对象实现的
                    // 然后对图片做缓存处理
                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [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);
                            }
                        });
                    }
                }
                //执行结束,将这个operation从我们的正在执行的operation数组里remove掉
                if (finished) {
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }
            }];
            //设置取消回调block
            operation.cancelBlock = ^{
                [subOperation cancel];
                
                @synchronized (self.runningOperations) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
            };
        }
        //如果在缓存中找到图片则直接执行完成回调,并把这个operation从我们的正在执行的operation数组里remove掉
        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;
}

到这里我们SDWebImage的源码讲解结束了,这里只是介绍了SDWebImage的主要实现,SDWebImage还实现了一些扩展,UIImageView、UIView、UIButton、MKAnnotationView等视图类,大家可以参考源码进行更深入的研究。

参考资料

南峰子的技术博客
叶孤城___的

你可能感兴趣的:(SDWebImage源码详解 - SDWebImage管理器SDWebImageManager)