菲兒 發表於 2022-8-25 11:46:30

源码解析ios开发SDWebImage方法

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>引言</li><li>源码解析</li><ul class="second_class_ul"><li>字典操作</li><li>看一下调用下载函数前的实例化过程</li><ul class="third_class_ul"><li>快速查找缓存的方法回调</li></ul></ul><li>开始进入查找函数</li><ul class="second_class_ul"></ul><li>总结一下函数调用</li><ul class="second_class_ul"><li>1.先调用</li><ul class="third_class_ul"></ul><li>2.设置图片</li><ul class="third_class_ul"></ul></ul></ul></div><p class="maodian"></p><h2>引言</h2>
<p>在着手写第二篇的时候,发现这个SDWebimage确实吧NSOperation用的太美了。确实可能帮你理解<code>NSOperation</code>和<code>NSOperationQueue</code>,当然还有Block的队列。还有一个<code>GCD</code>。</p>
<p>各位看官在看的时候可以着重的看看他的<code>operatinQueue</code>的队列。看看是怎么添加到队列的以及是怎么移除队列。在后面的文章就会提到他是怎么执行的。 还要注重看的就是以前用的<code>NSURLConnection</code>而现在用的<code>NSURLSession</code>下来了</p>
<p>最近在面试,发现有人问这个组件,所有读一读,应付一下面试吧!!</p>
<p class="maodian"></p><h2>源码解析</h2>
<p>废话不多说看源码。</p>
<ul><li>1:在组件中提供了很多类似这样的方法</li></ul>
<div class="jb51code"><pre class="brush:cpp;">- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;
</pre></div>
<ul><li>2:于是乎所有的方法都会调用下面的这个方法</li></ul>
<div class="jb51code"><pre class="brush:cpp;">- (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
</pre></div>
<ul><li>3:下面具体阅读代码 第一行执行的代码</li></ul>
<div class="jb51code"><pre class="brush:cpp;">//********1: 所有的设置控件图片都是经过该方法*******
NSString *validOperationKey = operationKey ?: NSStringFromClass();
//********2: 取消当前控件正在operations的队列*******
;
</pre></div>
<p>解析:</p>
<p>1.在第一行创建了<code>validOperationKey</code>的operationKey,是以当前扩展的类名命名。</p>
<p>2.执行<code>sd_cancelImageLoadOperationWithKey</code>方法</p>
<div class="jb51code"><pre class="brush:cpp;">- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    /**
   * 在该对象中,用runtime手动的添加一个字典属性。
   ### 说一下这里的operationDictionary
   ### 该字典的value是下载的操作,然而key是对视图和操作来做的标识字符串
    */
    SDOperationsDictionary *operationDictionary = ;
    /*
   * 在生成字典中的都是新的,所有都没有数据
   */
    id operations = operationDictionary;
    if (operations) {
      if (]) {
            for (id &amp;lt;SDWebImageOperation&amp;gt; operation in operations) {
                if (operation) {
                  ;
                }
            }
      } else if (){
            [(id&amp;lt;SDWebImageOperation&amp;gt;) operations cancel];
      }
      ;
    }
}
</pre></div>
<p class="maodian"></p><h3>字典操作</h3>
<p>在看一下去字典操作<code></code></p>
<div class="jb51code"><pre class="brush:cpp;">- (SDOperationsDictionary *)operationDictionary {
    /**
   ### 这里有一个疑问?
   这样创建出来的字典每一次都是新的
    虽然用了 static char loadOperationKey,但是没一次创建的地址都是新的,并且该字典还是空的。
   创建完成之后都直接返回了。不知道每次创建的都是新的并且还是空的字典,这样的开销会很大的??!!!,也希望各位看官给予解答啊???
   */
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &amp;amp;loadOperationKey);
    // 创建成功返回该字典,直接把该字典当做属性返回
    if (operations) {
      return operations;
    }
    operations = ;
    objc_setAssociatedObject(self, &amp;amp;loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}
</pre></div>
<p>看到这里我们在返回继续看 这个函数的代码有点长,耐心看完啊。。。</p>
<div class="jb51code"><pre class="brush:cpp;">- (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();
    /**2: 取消当前控件正在operations的队列
   * 至于为什么在该控件下载前都要清空该控件对应的所有的下载队列?
   * 可能是,如果要给控件赋值新的图片的话,之前所有的操作都和当前的操作无关,所有就取消吧
    *******/
    ;
    objc_setAssociatedObject(self, &amp;amp;imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    /**3:设置占位图片*/
    /**这里的意思就是options参数不是SDWebImageDelayPlaceholder,就执行以下操作   */
    if (!(options &amp;amp; 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*/
            ;
      });
    }
    /**4:菊花提示*/
    if (url) {
      // check if activityView is enabled or not
      if () {
            ;
      }
      __weak __typeof(self)wself = self;
      /**5:下载图片
         * 进入下载图片最重要的函数也是核心的函数了
         这个函数关联的函数较多,先简单过一下。
         */
      id &amp;lt;SDWebImageOperation&amp;gt; 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;
            ;
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                  return;
                }
                if (image &amp;amp;&amp;amp; (options &amp;amp; SDWebImageAvoidAutoSetImage) &amp;amp;&amp;amp; completedBlock) {
                  completedBlock(image, error, cacheType, url);
                  return;
                } else if (image) {
                  ;
                  ;
                } else {
                  if ((options &amp;amp; SDWebImageDelayPlaceholder)) {
                        ;
                        ;
                  }
                }
                if (completedBlock &amp;amp;&amp;amp; finished) {
                  completedBlock(image, error, cacheType, url);
                }
            });
      }];
      /**将行的下操作放到uiview的下载队列中的自定义的字典中去*/
      ;
    } else {
      dispatch_main_async_safe(^{
            ;
            if (completedBlock) {
                NSError *error = ;
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
      });
    }
}
</pre></div>
<p class="maodian"></p><h3>看一下调用下载函数前的实例化过程</h3>
<p>这个loadImageWithURL函数在SDWebImageManager,并且是用单列调用,</p>
<div class="jb51code"><pre class="brush:cpp;">+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&amp;amp;once, ^{
      instance = ;
    });
    return instance;
}
- (nonnull instancetype)init {
    /**
   ###此处有其他重要配置,下一篇文章解读
   ###此处有其他重要配置,下一篇文章解读
   ###此处有其他重要配置,下一篇文章解读
   */
    SDImageCache *cache = ;
    SDWebImageDownloader *downloader = ;
    return ;
}
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = )) {
      _imageCache = cache;
      _imageDownloader = downloader;
      /**这里的failedURLS是NSSet也就没重复的属性*/
      _failedURLs = ;
      _runningOperations = ;
    }
    return self;
}
</pre></div>
<p>上面都是初始化的操作,然后看下面的函数</p>
<div class="jb51code"><pre class="brush:cpp;">- (id &lt;SDWebImageOperation&gt;)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 - 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 = ;
    }
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    /*! 如果转换失败,url为nil 返回 */
    if (!) {
      url = nil;
    }
    /**5.1:搞一个新的下载队列*/
    /*! 这里就又__block和__weak的用法区别
   这里简单说说一下
   1:__block,在block函数里可以修改和阅读
   2:__weak可以避免循环引用,在给他设置新数据的时候,设置方法既不保留新值,也不释放旧值
   */
    /*! 说一下SDWebImageCombinedOperation
   他拥有了一个缓存队列和一个能取消执行的block,并且还遵守了SDWebImageOperation一个协议
   */
    __block SDWebImageCombinedOperation *operation = ;
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    /**5.2判断URL是否是下载失败的url*/
    BOOL isFailedUrl = NO;
    if (url) {
      /*! 创建一个互斥锁防止有其他线程同时修改该对象 */
      @synchronized (self.failedURLs) {
            isFailedUrl = ;
      }
    }
    /**
   * 5.3url不存在或者下载失败的url 则不在继续下载队列
   * 返回一个completedBlock
   */
    if (url.absoluteString.length == 0 || (!(options &amp; SDWebImageRetryFailed) &amp;&amp; isFailedUrl)) {
       url:url];
      return operation;
    }
    /**5.4把url添加在下载队列
   把operation加入到self.runningOperations的数组里面,
   并创建一个互斥线程锁来保护这个操作
   */
    @synchronized (self.runningOperations) {
      ;
    }
    /*! 获取image的url对应的key */
    NSString *key = ;
    /**5.5快速查找***缓存*****/
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
      /**
         * 这里的状态改变,在sd_cancelImageLoadOperationWithKey方法中调用代理函数时会改变该状态cancel
         * 如果该队列是取消状态,直接从下载队列中移除,此处有一个安全锁
         */
      if (operation.isCancelled) {
            ;
            return;
      }
      /**下载完成之后执行图片转换处理和缓存操作**/
      //条件1:在缓存中没有找到图片或者options选项里面包含了SDWebImageRefreshCached(这两项都需要进行请求网络图片的)
      //条件2:代理允许下载,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许
      if ((!cachedImage || options &amp; SDWebImageRefreshCached) &amp;&amp;
            (! || )) {
            //如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片
            if (cachedImage &amp;&amp; options &amp; 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 &amp;&amp; !operation.isCancelled &amp;&amp; completionBlock) {
                        completionBlock(image, data, error, cacheType, finished, url);
                  }
                });
               */
                ;
            }
            // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
            // download if no image or requested to refresh anyway, and download allowed by delegate
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options &amp; SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options &amp; SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options &amp; SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options &amp; SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options &amp; SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options &amp; SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options &amp; SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (options &amp; SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
            if (cachedImage &amp;&amp; options &amp; SDWebImageRefreshCached) {
                // force progressive off if image already cached but forced refreshing
                // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
                downloaderOptions &amp;= ~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) {
                  //进行完成回调
                  ;
                  if (   error.code != NSURLErrorNotConnectedToInternet
                        &amp;&amp; error.code != NSURLErrorCancelled
                        &amp;&amp; error.code != NSURLErrorTimedOut
                        &amp;&amp; error.code != NSURLErrorInternationalRoamingOff
                        &amp;&amp; error.code != NSURLErrorDataNotAllowed
                        &amp;&amp; error.code != NSURLErrorCannotFindHost
                        &amp;&amp; error.code != NSURLErrorCannotConnectToHost) {
                        //将失败的url添加到failedURLS的set中去
                        @synchronized (self.failedURLs) {
                            ;
                        }
                  }
                }
                else {
                  //如果有重试状态,将url从失败列表中移除
                  if ((options &amp; SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            ;
                        }
                  }
                  BOOL cacheOnDisk = !(options &amp; SDWebImageCacheMemoryOnly);
                  //options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
                  if (options &amp; SDWebImageRefreshCached &amp;&amp; cachedImage &amp;&amp; !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                  } else if (
                               //图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现
                               downloadedImage &amp;&amp;
                               (!downloadedImage.images || (options &amp; SDWebImageTransformAnimatedImage)) &amp;&amp;
                               ) {
                        /**
                         * dispatch_get_global_queue创建的一个//全局队列异步队列执行
                         */
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //调用代理方法完成图片transform
                            UIImage *transformedImage = ;
                            if (transformedImage &amp;&amp; finished) {
                              BOOL imageWasTransformed = !;
                              // pass nil if the image was transformed, so we can recalculate the data from the image
                              //对已经transform的图片进行缓存
                              ;
                            }
                            /*! 回到主线的调度 */
                            ;
                        });
                  } else {
                        //如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
                        if (downloadedImage &amp;&amp; finished) {
                            ;
                        }
                        /*! 回到主线的调度 */
                        ;
                  }
                }
                /**
               * 从正在进行的操作列表中移除这组合操作
               * 此处有一个安全锁保证线程安全
               */
                if (finished) {
                  ;
                }
            }];
            /**取消的回调*/
            operation.cancelBlock = ^{
                ;
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                ;
            };
            //在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项满足至少一项)
      } else if (cachedImage) {
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            ;
            ;
      } else {
            //缓存中没有扎到图片且代理不允许下载
            // Image not in cache and download disallowed by delegate
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            ;
            ;
      }
    }];
    return operation;
}
</pre></div>
<p>这个函数就进入了SDWebimage缓存的策略了。</p>
<p>先说一下他的这一个策略缓存。</p>
<p>*1:大家都是SDWebiamge都是先从缓存上查找,如果有就直接显示</p>
<p>*2:如果不存在就在沙盒中查找&nbsp;</p>
<ul><li>*2.1如果存在,则把沙盒中的图片添加到imageCache中,取出显示&nbsp;</li><li>*2.2 如果不存在在显示占位图,根据URL在operationCache是否存在下载操作&nbsp;</li></ul>
<p>*2.2.1 如果存在,说明该图片正在下载。</p>
<p>*2.2.2如果不存在,创建图片下载操作,放到operationCache中</p>
<ul><li>* 2.3 下载完成,将当前操作队列从operationCache中移除。并将下载的图片的添加在imageCache中。显示</li></ul>
<p>先慢慢体会一下。。。(停留30秒)</p>
<p class="maodian"></p><h2>开始进入查找函数</h2>
<div class="jb51code"><pre class="brush:cpp;">- (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 = ;
    if (image) {
      /**从内存在检查到有图片**/
      NSData *diskData = nil;
      if () {
            diskData = ;
      }
      /**如果有直接返回在view上显示*/
      if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
      }
      return nil;
    }
    /**如果内存上没有数据,则在硬盘上查找,如果找到了该图片,就放到缓存上用doneBlock完成回调**/
    /*! 这一块创建了异步队列
   这里的self.ioQueue是这样定义的
   ****@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
   ****这里开始了串行的队列去处理硬盘上的缓存
   */
    NSOperation *operation = ;
    dispatch_async(self.ioQueue, ^{
      /**如果是取消的 就直接返回*/
      if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
      }
      /*! 开了手动释放池 */
      @autoreleasepool {
            /**从磁盘中读取图片*/
            /*! 根据url去硬盘上查找数据 */
            NSData *diskData = ;
            UIImage *diskImage = ;
            if (diskImage &amp;&amp; self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                /**如果在硬盘中读取到图片,则把沙盒中的图片放到Cache中*/
                //self.memCache是NSCache创建的一个对象
                ;
            }
            if (doneBlock) {
                /*! 在主线程放回数据 */
                dispatch_async(dispatch_get_main_queue(), ^{
                  doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
      }
    });
    return operation;
}
</pre></div>
<p class="maodian"></p><h4>快速查找缓存的方法回调</h4>
<p>看完该函数以后在回到上面的看这个快速查找缓存的方法回调</p>
<div class="jb51code"><pre class="brush:cpp;">operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
      if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                ;
            }
            return;
      }
      if ((!image || options &amp; SDWebImageRefreshCached) &amp;&amp; (! || )) {
//如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片
            if (image &amp;&amp; options &amp; SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                // 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新
                  completedBlock(image, nil, cacheType, YES, url);
                });
            }
            // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
            SDWebImageDownloaderOptions downloaderOptions = 0;
            //开始各种options的判断
            if (options &amp; SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options &amp; SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options &amp; SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options &amp; SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options &amp; SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options &amp; SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options &amp; SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (image &amp;&amp; options &amp; SDWebImageRefreshCached) {
            // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
                downloaderOptions &amp;= ~SDWebImageDownloaderProgressiveDownload;
               // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            //创建下载操作,先使用self.imageDownloader下载
            id &lt;SDWebImageOperation&gt; 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 &amp;&amp; !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                  });
                  //将url添加到失败列表里面
                  if (   error.code != NSURLErrorNotConnectedToInternet
                        &amp;&amp; error.code != NSURLErrorCancelled
                        &amp;&amp; error.code != NSURLErrorTimedOut
                        &amp;&amp; error.code != NSURLErrorInternationalRoamingOff
                        &amp;&amp; error.code != NSURLErrorDataNotAllowed
                        &amp;&amp; error.code != NSURLErrorCannotFindHost
                        &amp;&amp; error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            ;
                        }
                  }
                }
                else {
                  //如果设置了下载失败重试,将url从失败列表中去掉
                  if ((options &amp; SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            ;
                        }
                  }
                  BOOL cacheOnDisk = !(options &amp; SDWebImageCacheMemoryOnly);
      //options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
                  if (options &amp; SDWebImageRefreshCached &amp;&amp; image &amp;&amp; !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
// 图片刷新遇到了NSSURLCache中有缓存的状况,不调用完成回调。
                }
//图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现
                  else if (downloadedImage &amp;&amp; (!downloadedImage.images || (options &amp; SDWebImageTransformAnimatedImage)) &amp;&amp; ) {
//全局队列异步执行                      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //调用代理方法完成图片transform
                            UIImage *transformedImage = ;
                            if (transformedImage &amp;&amp; finished) {
                              BOOL imageWasTransformed = !;
                //对已经transform的图片进行缓存
                              ;
                            }
                            //主线程执行完成回调
                            dispatch_main_sync_safe(^{
                              if (strongOperation &amp;&amp; !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                              }
                            });
                        });
                  }
//如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
                  else {
                        if (downloadedImage &amp;&amp; finished) {
                            ;
                        }
                   //主线程完成回调
                        dispatch_main_sync_safe(^{
                            if (strongOperation &amp;&amp; !strongOperation.isCancelled) {
                              completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                  }
                }
                if (finished) {
       // 从正在进行的操作列表中移除这组合操作
                  @synchronized (self.runningOperations) {
                        if (strongOperation) {
                            ;
                        }
                  }
                }
            }];
          //设置组合操作取消得得回调
            operation.cancelBlock = ^{
                ;
                @synchronized (self.runningOperations) {
                  __strong __typeof(weakOperation) strongOperation = weakOperation;
                  if (strongOperation) {
                        ;
                  }
                }
            };
      }
//处理其他情况
//case1.在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项满足至少一项)
      else if (image) {
            //完成回调
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation &amp;&amp; !strongOperation.isCancelled) {
                  completedBlock(image, nil, cacheType, YES, url);
                }
            });
          //从正在进行的操作列表中移除组合操作
            @synchronized (self.runningOperations) {
                ;
            }
      }
          //case2:缓存中没有扎到图片且代理不允许下载
      else {
      //主线程执行完成回调
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation &amp;&amp; !weakOperation.isCancelled) {
                  completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                }
            });
          //从正在执行的操作列表中移除组合操作
            @synchronized (self.runningOperations) {
                ;
            }
      }
    }];
</pre></div>
<p class="maodian"></p><h2>总结一下函数调用</h2>
<p class="maodian"></p><h3>1.先调用</h3>
<div class="jb51code"><pre class="brush:cpp;">- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;
</pre></div>
<p class="maodian"></p><h3>2.设置图片</h3>
<div class="jb51code"><pre class="brush:cpp;">- (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
</pre></div>
<ul><li>2.1 取消该控件对应的之前的所有的下载操作</li></ul>
<div class="jb51code"><pre class="brush:cpp;">- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
</pre></div>
<ul><li>2.2 开始根据图片的url做为key去查找</li></ul>
<div class="jb51code"><pre class="brush:cpp;">- (id &lt;SDWebImageOperation&gt;)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                 completed:(nullable SDInternalCompletionBlock)completedBlock
</pre></div>
<p>2.2.1 查找内存和硬盘上的缓存</p>
<div class="jb51code"><pre class="brush:cpp;">- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
</pre></div>
<ul><li>2.3 创建下载队列下载图片</li></ul>
<div class="jb51code"><pre class="brush:cpp;">- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
</pre></div>
<ul><li>2.4 最后将进行的操作,放到view对应的opationsDicaionary的字典中。记录当前的操作队列</li></ul>
<div class="jb51code"><pre class="brush:cpp;">- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key</pre></div>
<p>以上就是源码解析ios开发SDWebImage方法的详细内容,更多关于ios SDWebImage方法的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>iOS 图片加载框架SDWebImage解读</li><li>ios通过SDWebImage实现图片加载时的渐变效果</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: 源码解析ios开发SDWebImage方法