iOS源码阅读——YYWebImage

加载网络图片是项目中比较常见的需求,YYWebImage是Github中比较好用的图片加载开源框架之一。YYWebImage是作者为UIImageView创建的Category,使得UIImageView的实例能够直接调用一个实例方法,就能获取网络图片的功能。

UIImageView+YYWebImage的实例方法

UIImageView+YYWebImage列举了5中方法供开发者使用,分别是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
①- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder;
②- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options;
③- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
completion:(nullable YYWebImageCompletionBlock)completion;
④- (void)yy_setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
transform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;

⑤- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion;

从方法的具体实现中可以看到,①~④的方法实现,都是调用了方法⑤,只是参数传递有所不同。所以我们只需要着重阅读方法⑤的代码实现

具体实现——UIImageView+YYWebImage.m

  1. 判断传入的图片链接如果是NSString类型的实例,要先转换为NSURL类型的实例

  2. 三目运算,判断YYWebImageManager实例初始化与否,确保初始化完成

  3. _YYWebImageSetter

    1. 关于_YYWebImageSetter,作者的注解是:Private class used by web image categories.Typically, you should not use this class directly.它是Web图像类别使用的私有类。通常,您不应直接使用此类。
    2. 包含2个只读属性、和4个方法
      1. 属性:(NSURL*)imageURL、(int32_t) sentinel
      2. 方法:
        1. -(int32_t)setOperationWithSentinel: url: options: manager: progress: transform: completion:
        2. -(int32_t)cancel
        3. -(int32_t)cancelWithNewURL:
        4. +(dispatch_queue_t)setterQueue
      3. objc_getAssociatedObject:通过runtime获取UIImageView的动态属性setter,类型为_YYWebImageSetter
      4. 如果获取到的setter为空,则需要新建一个_YYWebImageSetter实例,并动态关联这个属性。
      5. [setter cancelWithNewURL:imageURL]通过新的图片链接,取消原来的线程操作。实现步骤:
        1. dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER)如果_lock的信号量值为0,则线程阻塞,不往下执行。否则,信号量-1(初始信号量为1),继续往下执行。
        2. 判断 _operation线程是否存在,如果存在,取消这个线程,并置空。
        3. 为 _imageURL变量赋值——新的图片连接
        4. sentinel = OSAtomicIncrement32(&_sentinel);值自增
        5. dispatch_semaphore_signal(_lock) 信号量+1
  4. _yy_dispatch_sync_on_main_queue确保在主线程执行后面的代码。

    1. 从内存缓存YYImageCacheTypeMemory中获取图片,如果获取成功,则执行complete(),结束操作。
    2. 未在内存缓存中获取到图片,继续执行。
      1. 通过 [_YYWebImageSetter setterQueue],获取到一个与全局队列优先级相同的队列,并异步执行后面的代码
      2. 预先创建和实现的 YYWebImageCompletionBlock _completion block。
      3. 实现 _YYWebImageSetter *setter的实例方法—— [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]。
      4. 通过一路追随 _completion block的传递,发现进入了 YYWebImageOperation 类。
      5. YYWebImageOperation继承于 NSOperation。作者重写了NSOpration类的start,方法。并在恰当时机调用了自定义方法 _startOperation。
      6. _startOperation方法的实现
        1. 当_cache存在且用户没有设置 YYWebImageOptionUseNSURLCache 和 YYWebImageOptionRefreshImageCache 选项。会先去取图片缓存。
        2. 获取内存图片缓存,调用方法 [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];。通过_cacheKey获取内存缓存。如果获取成功,结束后台线程;获取失败,则向下执行
        3. 当用户没有设置忽略磁盘缓存 YYWebImageOptionIgnoreDiskCache,则接下去,从磁盘获取图片缓存。
        4. 获取磁盘图片缓存,调用方法 [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk]。
          1. 如果从磁盘获取图片缓存成功,先将图片缓存到内存,并调用 _didReceiveImageFromDiskCache方法。
          2. 如果同磁盘获取图片缓存失败,则需要从网络请求图片数据。
        5. 通过网络请求获取图片。
          1. 通过 incrementNetworkActivityCount为请求数量+1
          2. 通过 connection: didReceiveData: 方法的回调,不断地获取数据。
          3. 在 connectionDidFinishLoading 的方法回调中,调用 _didReceiveImageFromWeb 方法。
          4. 通过 decrementNetworkActivityCount 为请求数量-1
          1. _didReceiveImageFromWeb成功从网络获取图片。将图片分别存入内存和磁盘缓存中。
        6. 回到之前实现的 _completion代码块。主要是一些动画特效。
  5. 通过completion:(nullable YYWebImageCompletionBlock)completion,将结果返回给开发者

总结

YYWebImage的实现合乎我们的直觉:

  • 查看缓存(1.内存;2.磁盘)
    • 缓存命中
      • 返回图片
      • 更新视图
    • 缓存未命中
      • 异步下载图片
      • 加入缓存
      • 更新视图

心得

之前尝试阅读过YYWebImage框架,当时的方法是一个类一个类看,从.h的属性、方法,到.m的具体实现。说实话,看起来着实有点烧脑和低效。读了Draven的《iOS 源代码分析 — SDWebImage》,有所启发,直接以API作为入口阅读源码,更具方向性,才会更有收获。
文章中涉及到了dispatch_semaphore队列优先级的知识。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信