您的当前位置:首页正文

OpenSource-SDWebImage

来源:图艺博知识网

类图

下图是官方文档中提供的SDWebImage库的类图,详细展现了该库的功能模块的划分和构成,可以整体上对这个库的层次结构有个了解

SDWebImageClassDiagram.png

时序图

下图是官方文档中提供的一张典型的图片加载过程,从调用category的sd_setImageWithURL接口到最后调用ImageView的setImage方法的完成图片加载的整个过程。那就以这张图为思路,逐步拆解分析和学习吧

SDWebImageSequenceDiagram.png

Category

SDWebImage主要通过category+block的方式提供接口调用,实现对原有工程代码无侵入,同时图片的加载进度及完成事件都以block方式回调,使用便捷,例如最常见的sd_setImageWithURL方法簇,在UIKit框架中的基础视图和常用控件中都有category的实现,例如UIImageView、UIView、UIButton、UIImage等。下面是几个比较常用的类别方法,最终都会调用到UIView (WebCache)sd_internalSetImageWithURL核心方法中去

    - (void)sd_setImageWithURL:(nullable NSURL *)url
      placeholderImage:(nullable UIImage *)placeholder;
      
    - (void)sd_setImageWithURL:(nullable NSURL *)url
      placeholderImage:(nullable UIImage *)placeholder
             completed:(nullable SDExternalCompletionBlock)completedBlock;
             
    - (void)sd_setImageWithURL:(nullable NSURL *)url
      placeholderImage:(nullable UIImage *)placeholder
               options:(SDWebImageOptions)options
              progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
             completed:(nullable SDExternalCompletionBlock)completedBlock;
     
    ............

Image URL

调用方式知道了,是category,那接下来第一个问题,就是我们请求加载的图片地址,是以什么方式保存管理的呢?答案是Runtime中的objc_setAssociatedObjectobjc_getAssociatedObject方法,在运行时动态的将url值绑定到具体的对象(例如ImageView)中,以imageURLKey全局变量作为绑定值的key,代码如下:

static char imageURLKey;
...
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
...
objc_getAssociatedObject(self, &imageURLKey);

类似的动态值绑定还有TAG_ACTIVITY_INDICATOR、TAG_ACTIVITY_STYLE、TAG_ACTIVITY_SHOW,用以控制图片加载时是否使用Indicator,Indicator的显示风格等

Load Error

在SDWebImage中比较常见的一个问题就是载入失败了的图片如何处理,在SDWebImageManager核心类中,发生过载入错误的图片URL会存入failedURLs中,在每次执行下载图片请求前会检测其URL是否曾经失败过,如果失败过并且没有设置失败重试标志(SDWebImageRetryFailed),SDWebImageManager会直接调用completedBlock返回错误,不会继续后面的网络下载操作

Cache

SDWebImageManager执行正式下载之前,会先通过Cache机制查找本地是否已经存在所请求的图片(以URL String做为key来匹配),保证最优性能

缓存机制在SDWebImage中有着很好的支持,由SDImageCache类负责管理,例如缓存位置(内存、磁盘),缓存空间(路径、大小限制),缓存周期等

  • 从Cache查询图片时,先从内存缓冲空间(NSCache)查找,如果命中,直接返回图片

  • 内存缓冲空间不存在,再从磁盘缓冲空间(diskCachePath)查找,如果命中,先判断config是否指定缓存在内存的标志位(shouldCacheImagesInMemory),如果指定,计算其占用大小,然后放到内存缓冲空间(NSCache)中,最后返回图片

  • 为了良好的性能及Cancel机制,在涉及到磁盘I/O操作的地方,SDImageCache使用了独立的GCD Dispatch_Queue来实现异步加载;以SDWebImageOperation为操作单位,标记URL与Cache Operation的对应关系,实现缓存读取的Cancel机制

  • Cache中提供了一些自动化的有效周期管理,在应用收到内存警告、进行后台或者退出时,根据设置的规则进行缓存清理工作,例如maxCacheAge,缓冲图片的最长保留时间;maxCacheSize,最大的缓存占用空间

Download

在缓存SDImageCache查询结束后,下载操作之前,仍会有些预判断条件,例如缓存是否命中、是否config中设置了强制刷新标志(SDWebImageRefreshCached)、Delegate询问是否可以加载图片(imageManager:shouldDownloadImageForURL:)等,SDWebImageManager确定是否需要执行真正下载流程

SDWebImageDownloader类是SDWebImage中真正负责调度执行下载任务的核心类,主要包括以下几方面:

  • NSURLSession,网络会话,共享给SDWebImageDownloaderOperation类使用,负责具体下载任务单元中的网络调用,必要时,下载任务单元中也会自己创建独立的Session使用

  • barrierQueue,同步队列,以用同步所有网络请求返回结果及下载任务,在Downloader添加新下载任务及取消下载任务都以这个串行队列同步执行,保证线程安全。在SDWebImageDownloaderOperation中也使用了barrierQueue,来同步callbackBlock回调的新增、删除和查询,保证同一时刻相同URL请求不会被重复下载。

  • URLOperations,字典,SDWebImageDownloader中,管理URL与下载任务(SDWebImageDownloaderOperation)的对应关系

  • callbackBlocks,字典型数组,SDWebImageDownloaderOperation中,管理progress与completed回调block

  • NSOperationQueue,下载任务执行队列,用以执行SDWebImageDownloaderOperation封装的具体下载任务

  • SDWebImageDownloaderOperation:下载任务执行单元,继承自NSOperation,实现NSURLConnectionDataDelegate与NSURLSessionTaskDelegate协议,定义了枚举类型SDWebImageDownloaderOptions用以控制下载的优先级、缓存、后台任务执行、渐进式下载、cookie处理以及证书认证几个方面,在创建下载操作的时候可以使用组合的选项以完成一些特殊的需求。

      typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
      SDWebImageDownloaderLowPriority = 1 << 0,
      SDWebImageDownloaderProgressiveDownload = 1 << 1,
    
      /**
       * By default, request prevent the use of NSURLCache. With this flag, NSURLCache
       * is used with default policies.
       */
      SDWebImageDownloaderUseNSURLCache = 1 << 2,
    
      /**
       * Call completion block with nil image/imageData if the image was read from NSURLCache
       * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
       */
    
      SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
      /**
       * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
       * extra time in background to let the request finish. If the background task expires the operation will be cancelled.
       */
    
      SDWebImageDownloaderContinueInBackground = 1 << 4,
    
      /**
       * Handles cookies stored in NSHTTPCookieStore by setting 
       * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
       */
      SDWebImageDownloaderHandleCookies = 1 << 5,
    
      /**
       * Enable to allow untrusted SSL certificates.
       * Useful for testing purposes. Use with caution in production.
       */
      SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
    
      /**
       * Put the image in the high priority queue.
       */
      SDWebImageDownloaderHighPriority = 1 << 7,
      
      /**
       * Scale down the image
       */
      SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
      };
    

总结

下面这张图是我按照自己对SDWebImage的功能模块、调用流程、所属线程的理解,大致画的一张简略的示意图:

SDWebImage.png

Tips:block循环引用的解决办法

所谓循环引用,即在实例对象的block块中使用self.语法,导致self实例持有block,而block也持有了self实例,通常的做法是使用__weak达到block始终不持有self实例,解除了循环引用关系,从双向变成了单向,但仍然存在风险,例如block块在执行期间,self实例已经销毁

在SDWebImage的代码中,通过在block块中再强持有前面声明的weak self,达到只在block块运行期间持有可用的self实例,同时仍然维持了单向引用关系,基本降低了crash的风险,非常巧妙的用法,如下:

        ......
// 声明弱引用实例指针变量,使block块不再持有实例
__weak __typeof(self)wself = self;

id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url 
      options:options 
      progress:progressBlock 
      completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
      
  // 块中声明强引用实例变量,持有弱引用实例指针变量,达到只在block块内执行期间持有可用的self实例
    __strong __typeof (wself) sself = wself;
    
    [sself sd_removeActivityIndicator];
        ......
Top