源码解析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方法的资料请关注我们其它相关文章!

(0)

相关推荐

  • ios通过SDWebImage实现图片加载时的渐变效果

    先上效果图: 这些图片是在我限制了网速的情况下加载的: 实现效果 思路解析 想到渐变属性的时候,自然而然的想起CATransition这个类 先看整体的实现代码: 首先找到UIImageView+WebCache.m这个文件中的- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageD

  • iOS 图片加载框架SDWebImage解读

    目的 在使用SDWebImage加载图片时,尤其是加载gif等大图时,SDWebImage会将图片缓存在内存中,这样是非常吃内存的,这时我们就需要在适当的时候去释放一下SDWebImage的内存缓存,才不至于造成APP闪退. SDWebImage提供了 UIImageView.UIButton .MKAnnotationView 的图片下载分类,只要一行代码就可以实现图片异步下载和缓存功能. 这样开发者就无须花太多精力在图片下载细节上,专心处理业务逻辑. SDWebImage 特点 提供 UII

  • 源码解析ios开发SDWebImage方法

    目录 引言 源码解析 字典操作 看一下调用下载函数前的实例化过程 快速查找缓存的方法回调 开始进入查找函数 总结一下函数调用 1.先调用 2.设置图片 引言 在着手写第二篇的时候,发现这个SDWebimage确实吧NSOperation用的太美了.确实可能帮你理解NSOperation和NSOperationQueue,当然还有Block的队列.还有一个GCD. 各位看官在看的时候可以着重的看看他的operatinQueue的队列.看看是怎么添加到队列的以及是怎么移除队列.在后面的文章就会提到他

  • jstorm源码解析之bolt异常处理方法

    问题 用过storm或者jstorm的都知道,如果在bolt代码中发生了没被catch住的异常,所在worker进程会退出.本文就从源码角度分析一下具体设计,其实并不是"有异常然后进程崩了"这么简单. 实质 我们先看BasicBoltExecutor的源码: public void execute(Tuple input) { _collector.setContext(input); try { _bolt.execute(input, _collector); _collector

  • Vue 2源码解析HTMLParserOptions.start函数方法

    目录 HTMLParserOptions.start() 处理后的 input ast element HTMLParserOptions.start() 用来解析标签的开始部分(匹配到标签开始部分时调用),主要区分标签类型.解析标签指令配置与动态绑定参数等等. let root let currentParent function start(tag, attrs, unary, start, end) { const ns = (currentParent && currentPare

  • Java源码解析HashMap的keySet()方法

    HashMap的keySet()方法比较简单,作用是获取HashMap中的key的集合.虽然这个方法十分简单,似乎没有什么可供分析的,但真正看了源码,发现自己还是有很多不懂的地方.下面是keySet的代码. public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } 从代码中了解到,第一次调用keySet方法时,keySet

  • Android音视频开发Media FrameWork框架源码解析

    目录 一.Media FrameWork背景 二.Media Framework“路线图” 2.1 代理端 2.2 服务端 2.2.1 Source 2.2.2 Decoder 2.2.3 Renderer 2.2.4 Foundation 2.3 OMX端 2.4 Kernel端 三.media播放的流程 四.Media FrameWork源码分析 一.Media FrameWork背景 Media Framework (媒体函数库):此函数库让Android 可以播放与录制许多常见的音频与视

  • jq源码解析之绑在$,jQuery上面的方法(实例讲解)

    1.当我们用$符号直接调用的方法.在jQuery内部是如何封装的呢?有没有好奇心? // jQuery.extend 的方法 是绑定在 $ 上面的. jQuery.extend( { //expando 用于决定当前页面的唯一性. /\D/ 非数字.其实就是去掉小数点. expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready wit

  • Spring SpringMVC在启动完成后执行方法源码解析

    关键字:spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件) 应用场景:很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用了其他类(可能是更复杂的关联),所以当我们去使用这个类做事情时发现包空指针错误,这是因为我们这个类有可能已经初始化完成,但是引用的其他类不一定初始化完成,所以发生了空指针错误,解决方案如下: 1.写一个类继承spring的ApplicationListener监听,并监控ContextRefresh

  • Java源码解析之HashMap的put、resize方法详解

    一.HashMap 简介 HashMap 底层采用哈希表结构 数组加链表加红黑树实现,允许储存null键和null值 数组优点:通过数组下标可以快速实现对数组元素的访问,效率高 链表优点:插入或删除数据不需要移动元素,只需要修改节点引用效率高 二.源码分析 2.1 继承和实现 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

  • Go Excelize API源码阅读Close及NewSheet方法示例解析

    目录 一.Go-Excelize简介 二.Close() 三.NewSheet() 一.Go-Excelize简介 Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准.可以使用它来读取.写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档. 支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式.图片(表).透视表.切片器等

  • Android源码解析onResume方法中获取不到View宽高

    目录 前言 问题1.为什么onCreate和onResume中获取不到view的宽高? 问题2.为什么View.post为什么可以获取View宽高? 结论 前言 有一个经典的问题,我们在Activity的onCreate中可以获取View的宽高吗?onResume中呢? 对于这类八股问题,只要看过都能很容易得出答案:不能. 紧跟着追问一个,那为什么View.post为什么可以获取View宽高? 今天来看看这些问题,到底为何? 今日份问题: 为什么onCreate和onResume中获取不到vie

随机推荐