SDWebImage 实现原理

SDWebImageClassDiagram.png

通过上面这张图我们可以得知SDWebImage中主要包含SDWebImageManagerSDWebImageCacheSDWebImageDownloader以及UIView+WebCache扩展类

  • SDWebImageManager 主要是对创建任务、判断是否包含下载任务、处理图片在本地还是需要网络请求逻辑(在loadImageWithURL中进行实现)
  • SDWebImageCache 主要是对图片生成的缓存进行处理,包括保存图片数据清理磁盘及图片缓存、查找相关路径、确定所占的最大内存
  • SDWebImageDownloader 主要负责对公共信息的处理(请求头的拼接、block设置、参数初始化等)、对图片下载进行处理,包含下载的最大并发数、对下载任务的相关操作以及反馈下载的进度及数据
  • UIView+WebCache是一个扩展类,主要是通过它将SDWebImageManager开放的相关方法进行调用然后反馈到UI界面

加载图片的流程

SDWebImageSequenceDiagram.png

从上图我们可以看出当调用SDWebImage进行图片渲染的时候,步骤如下:

  • 首先会通过UIView+WebCache扩展类就可以实现imageView调用sd_setImageWithURL方法
  • 然后根据sd_setImageWithURL的具体实现去调用sd_internalSetImageWithURL
  • 通过对任务的判断确保任务执行的先后顺序,然后去调用SDWebImageManager开放的方法loadImageWithURL
  • 这时就是我们熟悉的话语,先去查找缓存中是否存在,其中缓存分为内存缓存磁盘缓存,这一点SDWebImage实现了双缓存
    • 内存缓存是通过继承NSCache去获得相关的缓存文件,重写以及补充一些方法对外开放,更容易去对内存进行管理
      *磁盘缓存是创建一个目录,并为每一个缓存文件生成MD5文件名,更容易去进行查找
    • 如果缓存中存在对应的图片这时候就直接返回image
    • 如果缓存中不存在对应的图片数据则需要进行网络请求进行下载,此时用到的是SDWebImageDownloaderdownloadImageWithURL方法去进行下载
    • 将下载下来的图片进行返回,并同时将图片进行保存以保证下载在调用时不需要进行网络请求
  • 通过网络下载返回的图片或者磁盘中存在的图片进行渲染,最终实现UI页面上图片的显示

源码分析

- (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 {
  ...
  //通过递归锁去实现 保证任务的并发数以及执行的先后顺序
 [self sd_cancelImageLoadOperationWithKey:validOperationKey];   
 ...
 //创建任务 通过返回的operation并将其存储在NSMapTable中,从其中查看其中任务队列
 @weakify(self);
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            if (!self) { return; }
            // if the progress not been updated, mark it to complete state
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
  } 

上面是通过对指定任务的key值进行保存,以确定任务执行的先后顺序及相关任务状态

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                   options:(SDWebImageOptions)options
                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                 completed:(nullable SDInternalCompletionBlock)completedBlock {
     ...
      __weak typeof(strongOperation) weakSubOperation = strongOperation;
          strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
      //在此处是处理图片下载成功及失败的相关逻辑
      如果任务下载失败则需要调用任务失败的相关方法,如果需要返回失败的图片地址,则通过信号量的方式确保同时只有一个任务执行
                          [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url];

       if (shouldBlockFailedURL) {
                      LOCK(self.failedURLsLock);
                      [self.failedURLs addObject:url];
                      UNLOCK(self.failedURLsLock);
                  }    
     //如果任务下载成功,则通过本身的block方法返回对应的image数据,并保存一份在缓存中,方便下次查找时能够迅速找到对应的文件。
     @autoreleasepool {
                              UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                              
                              if (transformedImage && finished) {
                                  BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                  NSData *cacheData;
                                  // pass nil if the image was transformed, so we can recalculate the data from the image
                                  if (self.cacheSerializer) {
                                      cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
                                  } else {
                                      cacheData = (imageWasTransformed ? nil : downloadedData);
                                  }
                                  [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                              }
                              
                              [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];                                     

上述代码清晰地展示了加载图片流程

  • 先在缓存中进行查找,如果缓存存在则直接返回对应的image
  • 如果缓存中没有,则需要通过任务下载的方式进行下载,同时保存对应任务标识符,保证同时只有一个任务执行
  • 如果图片下载失败,则需要调用通知方法,方便及时返回失败的图片地址
  • 如果图片下载成功,则需要将成功的image返回,并同时在缓存中保存一份,方便下次进行查找时迅速找到
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容