SDWebImage源码详解 - SDWebImage管理器SD

作者: 凌巅 | 来源:发表于2016-05-04 01:50 被阅读606次

    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 <NSObject>
    
    @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 <SDWebImageManagerDelegate> 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 <SDWebImageOperation>)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<SDWebImageOperation>)downloadImageWithURL:options:progress:completed:,这个函数的具体实现:

    - (id <SDWebImageOperation>)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 <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) {
                        // 操作被取消,则不做任务事情
                    }
                    // 如果出错,则调用完成回调,并将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等视图类,大家可以参考源码进行更深入的研究。

    参考资料

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

    相关文章

      网友评论

      • mqw:sdWebImage为什么不直接把图片缓存在磁盘中,而是要同时缓存在内存和磁盘中,这样做有什么优点呢?
        凌巅:当然是为了快啊,先存在内存中,提高性能

      本文标题:SDWebImage源码详解 - SDWebImage管理器SD

      本文链接:https://www.haomeiwen.com/subject/pajrrttx.html