iOS——SDWebImage解读

前言

在iOS的图片加载框架中,SDWebImage占据了大半壁江山。它提供了UIImageView的一个分类,支持从网络中下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件。在项目中使用SDWebImage来管理图片加载相关操作可以极大地提高开发效率,让我们更加专注于业务逻辑实现。

SDWebImage简介

功能特性

  • 提供了一个UIImageView的category用来加载网络图片并且对网络图片的缓存进行管理
  • 采用异步方式来下载网络图片
  • 采用异步方式,使用内存+磁盘来缓存网络图片,拥有自动的缓存过期处理机制
  • 支持的图片格式包括 PNG,JPEG,GIF,Webp等
  • 支持GIF动图(4.0 之前的动图效果并不是太好,4.0 以后基于 FLAnimatedImage加载动图)
  • 支持后台图片解压缩处理
  • 支持Arm64
  • 同一个URL的图片不被重复下载
  • 失效,虚假的URL不会被无限重试
  • 耗时操作都在子线程,确保不会阻塞主线程
  • 使用GCD和ARC

官方图解

主序列图(Main Sequence Diagram)

请添加图片描述

顶层API图(Top Level API Diagram)

请添加图片描述

整体类图(Overall Class Diagram)

请添加图片描述

SDWebImage功能

缓存

为了减少网络流量的消耗,我们都希望下载下来的图片缓存到本地,下次再去获取同一张图片时,可以直接从本地获取,而不再从远程服务器获取。这样做的另一个好处是提升了用户体验,用户第二次查看同一幅图片时,能快速从本地获取图片直接呈现给用户,不用经过繁琐的网络请求。

SDWebImage 提供了对图片缓存的支持,而该功能是由SDImageCache 类来完成的。该类负责处理内存缓存及一个可选的磁盘缓存。其中磁盘缓存的写操作是异步的,这样就不会对 UI 操作造成影响。

SDWebImage 的图片缓存采用的是 Memory(内存)Disk(硬盘) 双重 Cache 机制,SDImageCache 中有一个叫做 memCache 的属性,它是一个 NSCache 对象,用于实现我们对图片的 Memory Cache内存缓存,其实就是接受系统的内存警告通知,然后清除掉自身的图片缓存。对于 Disk Cache磁盘缓存SDWebImage 会将图片存放到 NSCachesDirectory 目录中,然后为每一个缓存文件生成一个 md5 文件名,存放到文件中。

查找缓存机制如下:

  1. Memory(内存)中查找:SDImageCache 类的 queryDiskCacheForKey 方法用于查询图片缓存。queryDiskCacheForKey 方法先会查询 Memory Cache ,如果查找到就直接返回,反之进入下面的硬盘查找。

  2. Disk(磁盘) 中查找:如果 Memory Cache 查找不到, 就会查询 Disk Cache。就是如果 Disk Cache 查询成功,会把得到的图片再次设置到 Memory Cache 中, 以便最大化那些高频率展现图片的效率。如果找不到就进入下面的网络下载。

  3. 网络下载: imageDownloader 属性负责请求网络,下载图片数据。 如果下载失败,会把失败的图片地址写入 failedURLs 集合。这个集合用于存放所有加载失败图片的URL,用于 SDWebImage 的一个机制:对上次加载失败的图片拒绝再次加载。 也就是说,一张图片在本次会话加载失败了,如果再次加载就会直接拒绝,这样提高了图片加载的性能。如果下载图片成功了,接下来就会使用 [self.imageCache storeImage] 方法将它写入缓存 ,同时也会写入硬盘,并且调用 completedBlock 告诉前端显示图片。

  4. Disk(硬盘)缓存清理策略:SDWebImage 会在每次 APP 结束的时候执行清理任务。 清理缓存的规则分两步进行: 第一步先清除掉过期的缓存文件。 如果清除掉过期的缓存之后,空间还不够,那么执行第二步:按文件时间从早到晚排序,先清除最早的缓存文件,直到剩余空间达到要求。

内存缓存

内存缓存的处理是使用 NSCache 对象来实现的。NSCache 是一个类似于集合的容器。它存储 key-value 对,这一点类似于 NSDictionary 类。

我们通常用使用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃,所以有Disk(硬盘)缓存清理策略。

磁盘缓存

SDWebImage 使用 NSFileManager 对象来实现磁盘缓存,图片存储的位置位于Cache文件夹。另外,SDImageCache 还定义了一个串行队列,来异步存储图片。

SDImageCache 提供了大量方法来缓存、获取、移除及清空图片。而对于每个图片,为了方便地在内存或磁盘中对它进行这些操作,我们需要一个 key 值来索引它。 在内存中,我们将其作为 NSCachekey 值,而在磁盘中,我们用这个 key 作为图片的文件名。 对于一个远程服务器下载的图片,其 url 理所当然作为这个 key 值。

组织架构

请添加图片描述

核心类

  • SDWebImageDownloader: 负责维持图片的下载队列,是一个单例对象
  • SDWebImageDownloaderOperation: 负责真正的图片下载请求,一个自定义的并行Operation子类
  • SDImageCache: 负责SDWebImage的缓存工作,是一个单例对象
  • SDWebImageManager: 是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁
  • SDWebImageDecoder: 负责图片的解压缩
  • SDWebImagePrefetcher: 负责图片的预取
  • UIImageView+WebCache: 和其他的扩展都是与用户直接打交道的

概念框架

请添加图片描述

  • UIImageView+WebCacheUIButton+WebCache直接为表层的UIKit框架提供接口
  • SDWebImageManger负责处理和协调SDWebImageDownloaderSDWebImageCache,并与 UIKit层进行交互
  • SDWebImageDownloaderOperation真正执行下载请求,最底层的两个类为高层抽象提供支持

源码解读

从使用开始说UIImageView+WebCache

  • 引用 #import "UIImageView+WebCache.h" 头文件
  • 加载图片地址
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:imageView];
    
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.xxx.com/image.jpg"]];
  • SDWebImage也提供了下列其他的加载方法:
- (void)sd_setImageWithURL:(nullable NSURL *)url {
   
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
   
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
   
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context {
   
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {
   
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
   
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {
   
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock {
   
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock];
}               
  • 这些最终都是调用一个全能方法。全能方法除了必需的的图片地址,还提供了占位图、可选项、加载进度和完成回调。
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 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 {
   
    [self sd_setImageWithURL:url 
    		placeholderImage:placeholder 
    				 options:options 
    				 context:nil 
    				progress:progressBlock 
    			   completed:completedBlock];
}
  • 可以发现,全能方法并没有什么实际的实现,还是对另一个方法的封装,这个方法在另一个分类UIView+WebCache中,看看实现:
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
   
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                             context:context
                       setImageBlock:nil
                            progress:progressBlock
                           completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
   
                               if (completedBlock) {
   
                                   completedBlock(image, error, cacheType, imageURL);
                               }
                           }];
}
  • 这个方法也是调用了一个全能方法,下面是其核心实现:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock {
   
    if (context) {
   
        // copy to avoid mutable object
        // 复制以避免可变对象
        context = [context copy];
    } else {
   
        context = [NSDictionary dictionary];
    }
    // 生成一个有效的操作密钥
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    // 如果传入了参数就用传入的,否则就用当前类的类名
    if (!validOperationKey) {
   
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        // 通过操作键传递到下游,可用于跟踪操作或图像视图类
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    self.sd_latestOperationKey = validOperationKey;
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    // 如果没有选择延迟加载占位图
    if (!(options & SDWebImageDelayPlaceholder)) {
   
    	// 在主线程主队列中设置占位图
        dispatch_main_async_safe(^{
   
        	// 作为图片下载完成之前的替代图片
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    
    // 如果传入了图片链接
    if (url) {
   
        // reset the progress
        // 重置进度
        NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        // 获取图像加载进度
        if (imageProgress) {
   
        // 初始化图片加载进度
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
        
#if SD_UIKIT || SD_MAC
        // check and start image indicator
        // 是否显示进度条(小菊花)
        [self sd_startImageIndicator];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
		
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值