欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

源码解析ios开发SDWebImage方法,

来源: 开发者 投稿于  被查看 27742 次 评论:119

源码解析ios开发SDWebImage方法,


目录
  • 引言
  • 源码解析
    • 字典操作
    • 看一下调用下载函数前的实例化过程
      • 快速查找缓存的方法回调
  • 开始进入查找函数
    • 总结一下函数调用
      • 1.先调用
        • 2.设置图片

        引言

        在着手写第二篇的时候,发现这个SDWebimage确实吧NSOperation用的太美了。确实可能帮你理解NSOperationNSOperationQueue,当然还有Block的队列。还有一个GCD

        各位看官在看的时候可以着重的看看他的operatinQueue的队列。看看是怎么添加到队列的以及是怎么移除队列。在后面的文章就会提到他是怎么执行的。 还要注重看的就是以前用的NSURLConnection而现在用的NSURLSession下来了

        最近在面试,发现有人问这个组件,所有读一读,应付一下面试吧!!

        源码解析

        废话不多说看源码。

        • 1:在组件中提供了很多类似这样的方法
        - (void)sd_setImageWithURL:(nullable NSURL *)url;
        - (void)sd_setImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder;
        
        • 2:于是乎所有的方法都会调用下面的这个方法
        - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                          placeholderImage:(nullable UIImage *)placeholder
                                   options:(SDWebImageOptions)options
                              operationKey:(nullable NSString *)operationKey
                             setImageBlock:(nullable SDSetImageBlock)setImageBlock
                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                 completed:(nullable SDExternalCompletionBlock)completedBlock
        
        • 3:下面具体阅读代码 第一行执行的代码
        //********1: 所有的设置控件图片都是经过该方法*******
        NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
        //********2: 取消当前控件正在operations的队列*******
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
        

        解析:

        1.在第一行创建了validOperationKey的operationKey,是以当前扩展的类名命名。

        2.执行sd_cancelImageLoadOperationWithKey方法

        - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
            // Cancel in progress downloader from queue
            /**
             * 在该对象中,用runtime手动的添加一个字典属性。
             ### 说一下这里的operationDictionary
             ### 该字典的value是下载的操作,然而key是对视图和操作来做的标识字符串
            */
            SDOperationsDictionary *operationDictionary = [self operationDictionary];
            /*
             * 在生成字典中的都是新的,所有都没有数据
             */
            id operations = operationDictionary[key];
            if (operations) {
                if ([operations isKindOfClass:[NSArray class]]) {
                    for (id <SDWebImageOperation> operation in operations) {
                        if (operation) {
                            [operation cancel];
                        }
                    }
                } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
                    [(id<SDWebImageOperation>) operations cancel];
                }
                [operationDictionary removeObjectForKey:key];
            }
        }
        

        字典操作

        在看一下去字典操作[self operationDictionary]

        - (SDOperationsDictionary *)operationDictionary {
            /**
             ### 这里有一个疑问?
             这样创建出来的字典每一次都是新的
            虽然用了 static char loadOperationKey,但是没一次创建的地址都是新的,并且该字典还是空的。
             创建完成之后都直接返回了。不知道每次创建的都是新的并且还是空的字典,这样的开销会很大的??!!!,也希望各位看官给予解答啊???
             */
            SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
            // 创建成功返回该字典,直接把该字典当做属性返回
            if (operations) {
                return operations;
            }
            operations = [NSMutableDictionary dictionary];
            objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            return operations;
        }
        

        看到这里我们在返回继续看 这个函数的代码有点长,耐心看完啊。。。

        - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                          placeholderImage:(nullable UIImage *)placeholder
                                   options:(SDWebImageOptions)options
                              operationKey:(nullable NSString *)operationKey
                             setImageBlock:(nullable SDSetImageBlock)setImageBlock
                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                 completed:(nullable SDExternalCompletionBlock)completedBlock {
            //********1: 所有的设置控件图片都是经过该方法*******
            NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
            /**2: 取消当前控件正在operations的队列
             * 至于为什么在该控件下载前都要清空该控件对应的所有的下载队列?
             * 可能是,如果要给控件赋值新的图片的话,之前所有的操作都和当前的操作无关,所有就取消吧
            *******/
            [self sd_cancelImageLoadOperationWithKey:validOperationKey];
            objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            /**3:设置占位图片*/
            /**这里的意思就是options参数不是SDWebImageDelayPlaceholder,就执行以下操作   */
            if (!(options & SDWebImageDelayPlaceholder)) {
                /**
                 * 注意这的里宏定义
                 * #ifndef dispatch_main_async_safe
                 * #define dispatch_main_async_safe(block)\
                 * if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
                 * block();\
                 * } else {\
                 * dispatch_async(dispatch_get_main_queue(), block);\
                 * }
                 * #endif
                 可以看出都把block方法主线程中去执行。
                 应为在系统里,只能有一个线程去执行UI的更新,那就是主线程。
                 如果能在其他线程更新,那这世界就乱了,像了解
                 更仔细的问题可以关注www.osjoin.com查看是为什么!
                 */
                dispatch_main_async_safe(^{
                    /**设置占位图placeholder*/
                    [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                });
            }
            /**4:菊花提示*/
            if (url) {
                // check if activityView is enabled or not
                if ([self sd_showActivityIndicatorView]) {
                    [self sd_addActivityIndicator];
                }
                __weak __typeof(self)wself = self;
                /**5:下载图片
                 * 进入下载图片最重要的函数也是核心的函数了
                 这个函数关联的函数较多,先简单过一下。
                 */
                id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                    __strong __typeof (wself) sself = wself;
                    [sself sd_removeActivityIndicator];
                    if (!sself) {
                        return;
                    }
                    dispatch_main_async_safe(^{
                        if (!sself) {
                            return;
                        }
                        if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                            completedBlock(image, error, cacheType, url);
                            return;
                        } else if (image) {
                            [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                            [sself sd_setNeedsLayout];
                        } else {
                            if ((options & SDWebImageDelayPlaceholder)) {
                                [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                                [sself sd_setNeedsLayout];
                            }
                        }
                        if (completedBlock && finished) {
                            completedBlock(image, error, cacheType, url);
                        }
                    });
                }];
                /**将行的下操作放到uiview的下载队列中的自定义的字典中去*/
                [self sd_setImageLoadOperation:operation forKey:validOperationKey];
            } else {
                dispatch_main_async_safe(^{
                    [self sd_removeActivityIndicator];
                    if (completedBlock) {
                        NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                        completedBlock(nil, error, SDImageCacheTypeNone, url);
                    }
                });
            }
        }
        

        看一下调用下载函数前的实例化过程

        这个loadImageWithURL函数在SDWebImageManager,并且是用单列调用,

        + (nonnull instancetype)sharedManager {
            static dispatch_once_t once;
            static id instance;
            dispatch_once(&once, ^{
                instance = [self new];
            });
            return instance;
        }
        - (nonnull instancetype)init {
            /**
             ###此处有其他重要配置,下一篇文章解读
             ###此处有其他重要配置,下一篇文章解读
             ###此处有其他重要配置,下一篇文章解读
             */
            SDImageCache *cache = [SDImageCache sharedImageCache];
            SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
            return [self initWithCache:cache downloader:downloader];
        }
        - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
            if ((self = [super init])) {
                _imageCache = cache;
                _imageDownloader = downloader;
                /**这里的failedURLS是NSSet也就没重复的属性*/
                _failedURLs = [NSMutableSet new];
                _runningOperations = [NSMutableArray new];
            }
            return self;
        }
        

        上面都是初始化的操作,然后看下面的函数

        - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                             options:(SDWebImageOptions)options
                                            progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                           completed:(nullable SDInternalCompletionBlock)completedBlock {
            // 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.
            /**
             这里防止用户输入的类型错误,转换一下
             */
            if ([url isKindOfClass:NSString.class]) {
                url = [NSURL URLWithString:(NSString *)url];
            }
            // Prevents app crashing on argument type error like sending NSNull instead of NSURL
            /*! 如果转换失败,url为nil 返回 */
            if (![url isKindOfClass:NSURL.class]) {
                url = nil;
            }
            /**5.1:搞一个新的下载队列*/
            /*! 这里就又__block和__weak的用法区别
             这里简单说说一下
             1:__block,在block函数里可以修改和阅读
             2:__weak可以避免循环引用,在给他设置新数据的时候,设置方法既不保留新值,也不释放旧值
             */
            /*! 说一下SDWebImageCombinedOperation
             他拥有了一个缓存队列和一个能取消执行的block,并且还遵守了SDWebImageOperation一个协议
             */
            __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
            __weak SDWebImageCombinedOperation *weakOperation = operation;
            /**5.2判断URL是否是下载失败的url*/
            BOOL isFailedUrl = NO;
            if (url) {
                /*! 创建一个互斥锁防止有其他线程同时修改该对象 */
                @synchronized (self.failedURLs) {
                    isFailedUrl = [self.failedURLs containsObject:url];
                }
            }
            /**
             * 5.3url不存在或者下载失败的url 则不在继续下载队列
             * 返回一个completedBlock
             */
            if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
                return operation;
            }
            /**5.4把url添加在下载队列
             把operation加入到self.runningOperations的数组里面,
             并创建一个互斥线程锁来保护这个操作
             */
            @synchronized (self.runningOperations) {
                [self.runningOperations addObject:operation];
            }
            /*! 获取image的url对应的key */
            NSString *key = [self cacheKeyForURL:url];
            /**5.5快速查找***缓存*****/
            operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
                /**
                 * 这里的状态改变,在sd_cancelImageLoadOperationWithKey方法中调用代理函数时会改变该状态cancel
                 * 如果该队列是取消状态,直接从下载队列中移除,此处有一个安全锁
                 */
                if (operation.isCancelled) {
                    [self safelyRemoveOperationFromRunning:operation];
                    return;
                }
                /**下载完成之后执行图片转换处理和缓存操作**/
                //条件1:在缓存中没有找到图片或者options选项里面包含了SDWebImageRefreshCached(这两项都需要进行请求网络图片的)
                //条件2:代理允许下载,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许
                if ((!cachedImage || options & SDWebImageRefreshCached) &&
                    (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
                    //如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片
                    if (cachedImage && options & SDWebImageRefreshCached) {
                        // 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但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新
                        dispatch_main_async_safe(^{
                            if (operation && !operation.isCancelled && completionBlock) {
                                completionBlock(image, data, error, cacheType, finished, url);
                            }
                        });
                         */
                        [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
                    }
                    // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
                    // 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 (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
                    if (cachedImage && options & SDWebImageRefreshCached) {
                        // force progressive off if image already cached but forced refreshing
                        // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
                        downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                        // ignore image read from NSURLCache if image if cached but force refreshing
                        // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image
                        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
                    }
                    /**如果在缓存和硬盘上都没查到url对应的图片
                     ***则进行图片下载
                     */
                    /*! 进入下载操作就是2.2中的操作了*/
                    SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url
                                                                                                    options:downloaderOptions
                                                                                                   progress:progressBlock
                                                                                                  completed:^(UIImage *downloadedImage,
                                                                                                              NSData *downloadedData,
                                                                                                              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
                            //如果我们调用completedBlock,这个block会和另外一个completedBlock争夺一个对象,因此这个block被调用后会覆盖新的数据
                        } else if (error) {
                            //进行完成回调
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock error: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) {
                                //将失败的url添加到failedURLS的set中去
                                @synchronized (self.failedURLs) {
                                    [self.failedURLs addObject:url];
                                }
                            }
                        }
                        else {
                            //如果有重试状态,将url从失败列表中移除
                            if ((options & SDWebImageRetryFailed)) {
                                @synchronized (self.failedURLs) {
                                    [self.failedURLs removeObject:url];
                                }
                            }
                            BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                            //options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
                            if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                                // Image refresh hit the NSURLCache cache, do not call the completion block
                            } else if (
                                       //图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现
                                       downloadedImage &&
                                       (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) &&[self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]
                                       ) {
                                /**
                                 * dispatch_get_global_queue创建的一个//全局队列异步队列执行
                                 */
                                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                    //调用代理方法完成图片transform
                                    UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                                    if (transformedImage && finished) {
                                        BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                        // pass nil if the image was transformed, so we can recalculate the data from the image
                                        //对已经transform的图片进行缓存
                                        [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                                    }
                                    /*! 回到主线的调度 */
                                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                                });
                            } else {
                                //如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
                                if (downloadedImage && finished) {
                                    [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                                }
                                /*! 回到主线的调度 */
                                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                            }
                        }
                        /**
                         * 从正在进行的操作列表中移除这组合操作
                         * 此处有一个安全锁保证线程安全
                         */
                        if (finished) {
                            [self safelyRemoveOperationFromRunning:strongOperation];
                        }
                    }];
                    /**取消的回调*/
                    operation.cancelBlock = ^{
                        [self.imageDownloader cancel:subOperationToken];
                        __strong __typeof(weakOperation) strongOperation = weakOperation;
                        [self safelyRemoveOperationFromRunning:strongOperation];
                    };
                    //在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项  满足至少一项)
                } else if (cachedImage) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
                    [self safelyRemoveOperationFromRunning:operation];
                } else {
                    //缓存中没有扎到图片且代理不允许下载
                    // Image not in cache and download disallowed by delegate
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
                    [self safelyRemoveOperationFromRunning:operation];
                }
            }];
            return operation;
        }
        

        这个函数就进入了SDWebimage缓存的策略了。

        先说一下他的这一个策略缓存。

        *1:大家都是SDWebiamge都是先从缓存上查找,如果有就直接显示

        *2:如果不存在就在沙盒中查找 

        • *2.1如果存在,则把沙盒中的图片添加到imageCache中,取出显示 
        • *2.2 如果不存在在显示占位图,根据URL在operationCache是否存在下载操作 

        *2.2.1 如果存在,说明该图片正在下载。

        *2.2.2如果不存在,创建图片下载操作,放到operationCache中

        • * 2.3 下载完成,将当前操作队列从operationCache中移除。并将下载的图片的添加在imageCache中。显示

        先慢慢体会一下。。。(停留30秒)

        开始进入查找函数

        - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
            /**从缓存中查找图片开始*/
            /*! 检查key是否为空(URL) */
            if (!key) {
                if (doneBlock) {
                    doneBlock(nil, nil, SDImageCacheTypeNone);
                }
                return nil;
            }
            // 先检查缓存--内存上的数据
            // First check the in-memory cache...
            UIImage *image = [self imageFromMemoryCacheForKey:key];
            if (image) {
                /**从内存在检查到有图片**/
                NSData *diskData = nil;
                if ([image isGIF]) {
                    diskData = [self diskImageDataBySearchingAllPathsForKey:key];
                }
                /**如果有直接返回在view上显示*/
                if (doneBlock) {
                    doneBlock(image, diskData, SDImageCacheTypeMemory);
                }
                return nil;
            }
            /**如果内存上没有数据,则在硬盘上查找,如果找到了该图片,就放到缓存上用doneBlock完成回调**/
            /*! 这一块创建了异步队列
             这里的self.ioQueue是这样定义的
             ****@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
             ****这里开始了串行的队列去处理硬盘上的缓存
             */
            NSOperation *operation = [NSOperation new];
            dispatch_async(self.ioQueue, ^{
                /**如果是取消的 就直接返回*/
                if (operation.isCancelled) {
                    // do not call the completion if cancelled
                    return;
                }
                /*! 开了手动释放池 */
                @autoreleasepool {
                    /**从磁盘中读取图片*/
                    /*! 根据url去硬盘上查找数据 */
                    NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
                    UIImage *diskImage = [self diskImageForKey:key];
                    if (diskImage && self.config.shouldCacheImagesInMemory) {
                        NSUInteger cost = SDCacheCostForImage(diskImage);
                        /**如果在硬盘中读取到图片,则把沙盒中的图片放到Cache中*/
                        //self.memCache是NSCache创建的一个对象
                        [self.memCache setObject:diskImage forKey:key cost:cost];
                    }
                    if (doneBlock) {
                        /*! 在主线程放回数据 */
                        dispatch_async(dispatch_get_main_queue(), ^{
                            doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                        });
                    }
                }
            });
            return operation;
        }
        

        快速查找缓存的方法回调

        看完该函数以后在回到上面的看这个快速查找缓存的方法回调

        operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
                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])) {
        //如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片
                    if (image && options & SDWebImageRefreshCached) {
                        dispatch_main_sync_safe(^{
                        // 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新
                            completedBlock(image, nil, cacheType, YES, url);
                        });
                    }
                    // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
                    SDWebImageDownloaderOptions downloaderOptions = 0;
                    //开始各种options的判断
                    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) {
                    // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
                        downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                       // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image
                        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
                    }
                    //创建下载操作,先使用self.imageDownloader下载
                    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
                            //如果操作取消了,不做任何事情
                            // 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
                        //如果我们调用completedBlock,这个block会和另外一个completedBlock争夺一个对象,因此这个block被调用后会覆盖新的数据
                        }
                        else if (error) {
                            //进行完成回调
                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(nil, error, SDImageCacheTypeNone, finished, 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从失败列表中去掉
                            if ((options & SDWebImageRetryFailed)) {
                                @synchronized (self.failedURLs) {
                                    [self.failedURLs removeObject:url];
                                }
                            }
                            BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                //options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
                            if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                                // Image refresh hit the NSURLCache cache, do not call the completion block
         // 图片刷新遇到了NSSURLCache中有缓存的状况,不调用完成回调。
                        }
          //图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现
                            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), ^{
                                    //调用代理方法完成图片transform
                                    UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                                    if (transformedImage && finished) {
                                        BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                        //对已经transform的图片进行缓存
                                        [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);
                                        }
                                    });
                                });
                            }
        //如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
                            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);
                                    }
                                });
                            }
                        }
                        if (finished) {
               // 从正在进行的操作列表中移除这组合操作
                            @synchronized (self.runningOperations) {
                                if (strongOperation) {
                                    [self.runningOperations removeObject:strongOperation];
                                }
                            }
                        }
                    }];
                  //设置组合操作取消得得回调
                    operation.cancelBlock = ^{
                        [subOperation cancel];
                        @synchronized (self.runningOperations) {
                            __strong __typeof(weakOperation) strongOperation = weakOperation;
                            if (strongOperation) {
                                [self.runningOperations removeObject:strongOperation];
                            }
                        }
                    };
                }
        //处理其他情况
        //case1.在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项  满足至少一项)
                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];
                    }
                }
                  //case2:缓存中没有扎到图片且代理不允许下载
                else {
                //主线程执行完成回调
                    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];
                    }
                }
            }];
        

        总结一下函数调用

        1.先调用

        - (void)sd_setImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder;
        

        2.设置图片

        - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                          placeholderImage:(nullable UIImage *)placeholder
                                   options:(SDWebImageOptions)options
                              operationKey:(nullable NSString *)operationKey
                             setImageBlock:(nullable SDSetImageBlock)setImageBlock
                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                 completed:(nullable SDExternalCompletionBlock)completedBlock
        
        • 2.1 取消该控件对应的之前的所有的下载操作
        - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
        
        • 2.2 开始根据图片的url做为key去查找
        - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                             options:(SDWebImageOptions)options
                                            progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                           completed:(nullable SDInternalCompletionBlock)completedBlock
        

        2.2.1 查找内存和硬盘上的缓存

        - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
        
        • 2.3 创建下载队列下载图片
        - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                           options:(SDWebImageDownloaderOptions)options
                                                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                         completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
        
        • 2.4 最后将进行的操作,放到view对应的opationsDicaionary的字典中。记录当前的操作队列
        - (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key

        以上就是源码解析ios开发SDWebImage方法的详细内容,更多关于ios SDWebImage方法的资料请关注3672js教程其它相关文章!

        您可能感兴趣的文章:
        • iOS 图片加载框架SDWebImage解读
        • ios通过SDWebImage实现图片加载时的渐变效果

        用户评论