SDWebImage源码解析

2020-03-03  本文已影响0人  pengxiaochao

SDWebImage相信大家都不陌生,项目中也经常用到,很久之前我也有阅读源码的习惯,但是一直没有开笔写一写源码解析,怕自己写的不好讲不清楚做了这么一个流程图,还望各位看官海涵🤦‍♂️,SDWebImage 的基本流程如下(顺便打个水印做个版权保护😂),当前使用的 SDWebImage版本号是 '5.1.1

SDWebImage流程图概要.jpg

为了方便读完本文能对SDWebImage 中的其它类也有所了解,特制作了如下思维导图帮助理解SDWebImage 的文件目录(建议可以先看)

189721583239321_.pic_hd.jpg

项目中的一些SDWebImage 的API ,统一采用简写的方式 ,例如:

//简写前
 - (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 {
}
//简写后
 - (void)sd_setImageWithURL: placeholderImage: options: context: progress: completed:{
}

SDWebImage 常见用法展示

//常见用法
[self.imageview.sd_setImageWithURL:[NSURL URLWithString:URLString] placeholderImage:[UIImage imageNamed:@"placeImage"]];

1.传入URL和占位图以及一些其它参数

如下代码所示意, URL链接最终进入到了UIImageView+WebCache分类的- (void) sd_setImageWithURL: placeholderImage: options: progress: completed:{} 的方法 ,并继续向下调用 UIView+WebCache 中的- (void)sd_internalSetImageWithURL: placeholderImage: options:context: setImageBlock: progress: completed: 方法 ;
该方法定义在UIView+WebCache 分类中 ,目的是给view添加赋值image对象的功能,方便扩展UIButton、UIImageView等控件添加网络图片;
下面我们接着往下看;

  //当前在 UIImageView+WebCache 文件
  //调用最底层的 sd_setImageWithURL 方法
  - (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 {

        //最终调用 UIView 分类的sd_internalSetImageWithURL 方法
        [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);
                             }
                         }];
}

2. 分析 UIView+WebCache 实现了哪些功能步骤

进入 UIView+WebCache-(void)sd_internalSetImageWithURL: placeholderImage: options:context: setImageBlock: progress: completed:{} 方法中我们可以看到,该函数做了如下事情;

  1. 如果正在对当前的URL进行下载,则取消
  2. 如果配置了占位图,设置placeHolderImage
  3. 开始 ImageIndicator 动画
  4. 加载图片流程(网络请求 or 本地缓存)
  5. 给view 的URL属性绑定operation 属性(runtime实现)

由于篇幅原因,部分不重要代码以 省略号...显示,

//当前在 UIView+WebCache文件中
//获取当前对象的URL key
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
//如果contex 不存在,默认key 为该对象的类名
if (!validOperationKey) {
     validOperationKey = NSStringFromClass([self class]);
}

//1.取消正在下载操作的网络图片请求(61行)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];

// 2.设置占位图(64行)
if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
   }

//3.开始加载图片前的loading 动画效果(80行)
[self sd_startImageIndicator];

// 4.开始获取图片操作 SDWebImageManager实现(111行)
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) {

   //获取图片成功进入该block,赋值给当前View
   //停止加载动画的loading效果
  ...
}

//5.给View 的validOperationKey 属性绑定 SDWebImageOperation类型的属性值 (176 行)
[self sd_setImageLoadOperation:operation forKey:validOperationKey];

3. 分析SDWebImageManager 加载图片逻辑

至此,加载图片的操作已经转移给SDWebImageManager单例来完成,UIView +WebCache分类,就是完成了给UIView 对象绑定一些自定义对象属性,比如URL对应的key,和判断该View绑定的URL 获取图片的Operaction对象,方便下次该view 再次请求图片的时候,直接通过属性查找完成;
SDWebImageManager 实现网络加载图片主要用了 这个方法 - (SDWebImageCombinedOperation *)loadImageWithURL: options: context: progress: completed:{ }

//当前在 SDWebImageManager 文件中
//真正 加载网络图片的操作是在这个函数中
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {

   省略一部分代码...
   //该代码块用来判断该URL是否是之前请求失败的地址
   //如果是失败的地址,return 一个operation 出来,结束
   BOOL isFailedUrl = NO;
    if (url) {
        SD_LOCK(self.failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(self.failedURLsLock);
    }

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
        return operation;
    }
    // 这个才是真正要关注的逻辑(加载网络请求加载图片)or(加载之前缓存的本地的图片)
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
    return operation;
}

4 分析- (void)callCacheProcessForOperation: url: options: context: progress:completed: {} 是如何实现 网络请求获取本地缓存

分析- (void)callCacheProcessForOperation: url: options: context: progress:completed: {} 方法是如何实现 网络请求获取本地缓存 这两种情况的;

// Query cache process
- (void)callCacheProcessForOperation: url: options: context:progress: completed: {

//通过配置,判断是否查找缓存
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);

if (shouldQueryCache) {
     //1.第一种情况查找缓存的情况
    operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
           
           此处省略部分代码...
           if(!operation || operation.isCancelled ) {
               //如operation 被取消 ,则 return 
             return 
           }
           else{
               //继续从网络请求资源图片
               //调用 self callDownloadProcessForOperation:operation方法
           }
     }];

} else {
       //2.第二种获取网络图片
       [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
   }
}
4.1 获取本地缓存的资源图片

查询本地缓存通过self.imageCache 这个属性来操作 ,imageCache是一个 [SDImageCache sharedImageCache]单例实现的;
最后跟踪 到 SDImageCache 类中的 - (NSOperation *)queryCacheOperationForKey: options: context: done: {}方法比较关键

// SDImageCache.m内部
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {

    省略部分代码...

    //1.通过内存获UIImage 的方法 (378行)
    UIImage *image = [self imageFromMemoryCacheForKey:key];

    //2.此处定义block 代码块,通过磁盘查找图片数据
    @autoreleasepool {
      void(^queryDiskBlock)(void) =  ^{
          NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
          UIImage *diskImage;
          if(image){
              //此处为从内存中取到图片数据的逻辑(处理缓存类型配置)
          }else if(diskData){
              //此处为从磁盘本地获取图片数据后的操作(处理缓存类型配置)
          }
      }

    }
    //3.分别同步或者异步执行block查找磁盘的图片数据
    if (shouldQueryDiskSync) {
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
}

较为关键的两个属性 self.memoryCacheself.diskCache分别实现了如如何从内存中获取图片和从硬盘中获取图片;
分别是SDMemoryCache类型 和 SDDiskCache 类型,具体实现细节,可以找到该类查看;

// SDImageCache.m内部
//通过内存获取图片数据的操作(290行)
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memoryCache objectForKey:key];
}
// 通过磁盘获取代码的操作(316行)
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
     其它代码省略....
     NSData *data = [self.diskCache dataForKey:key];
    return data;
}
4.2 获取网络图片

获取网络图片通过self.imageLoader 这个属性来操作 , imageLoader是一个[SDWebImageDownloader sharedDownloader] 单例实现的;

//SDWebImageManager.m 文件内
//加载网络上的图片资源的操作 (310行)
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                   url:(nonnull NSURL *)url
                               options:(SDWebImageOptions)options
                               context:(SDWebImageContext *)context
                           cachedImage:(nullable UIImage *)cachedImage
                            cachedData:(nullable NSData *)cachedData
                             cacheType:(SDImageCacheType)cacheType
                              progress:(nullable SDImageLoaderProgressBlock)progressBlock
                             completed:(nullable SDInternalCompletionBlock)completedBlock {

   省略部分代码...
   //通过self.imageLoader 获取当前请求的operation
   operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {

         省略逻辑代码代码...
         //在这里拿到图片的逻辑
         //或者网络图片请求失败的逻辑
    }];

   //最后将operation 从self.runningOperations 数组中移除
   [self safelyRemoveOperationFromRunning:operation];
}

网络请求的具体操作由SDWebImageDownloader实现,具体实现可查看其相关类,SD的源码写的真的的挺好,作者的架构思路值得学习

至此SDWebImage 的源码流程分析已经告一段落,由于篇幅原因,暂时讲解至此;

5.谢谢你耐心看完我的博客

上一篇 下一篇

猜你喜欢

热点阅读