iOS面试资料

SDWebImage 源码分析

2019-05-10  本文已影响78人  Kare

主要围绕三个方面来阐述

加载流程

时序图

这是从 github SDWebImage 地址下载的图.
图片说明了整个图片加载的时序.

  1. 首先是根据地址去缓存中取图片,此处的缓存是双缓存(磁盘 + 内存)
  2. 如果找到了就交付到上层处理/显示
  3. 如果没有都没找到,就会去网络下载图片
  4. 下载完了交付上层处理/显示
  5. 另外对下载的图片进行缓存

下面根这源码来验证一下上面说的知识
根据 Github 上面提供的整个 SD 的类图可知,主要是 SDWebImageManager 在操作管理查找流程.
我们就根据大家使用的最多的方法, 来一步步跟踪.

  1. 通常情况下,我们使用这个方法对图片进行加载。
UIImageView+WebCache.h 文件中
-(void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
  1. 查找 sd_internalSetImageWithURL 方法.
UIView+WebCache.h 文件中
sd_internalSetImageWithURL ....
  1. 如果你设置了 PlaceholderImage, 他会先去显示你的 placeholderImage
if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
  1. 继续去 SDWebImageManager 中的 loadImageWithURL 查找图片
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) {
  1. 然后调用 callCacheProcessForOperation 进行缓存查找操作
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
    // 缓存查找
}else {
    // 进行下载操作
}
  1. 接上缓存查找过程, 回去 SDImageCache 中的 queryImageForKey 方法继续缓存查找
-(id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
  1. 接下来到 queryCacheOperationForKey 方法
    • 首先去内存中找,如果有就用 Block 的方式传递到上层显示
    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    • 否则就去 Disk 查找
    NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
    
    • 如果 Disk 有就返回,并且把这个值存入 MemoryCache 中,这样就可以再下次查找中更快的找到对应的图片信息
    [self.memCache setObject:diskImage forKey:key cost:cost];
    
  1. 如果没找到就去下载图片
callDownloadProcessForOperation
  1. 进入下载流程, 生成 SDWebImageDownloadToken 对象.就行下载
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
  1. 下载完成就行存储
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
* 分别存储到内存和磁盘
```
-(void)storeImage:(nullable UIImage *)image
     imageData:(nullable NSData *)imageData
        forKey:(nullable NSString *)key
      toMemory:(BOOL)toMemory
        toDisk:(BOOL)toDisk
    completion:(nullable SDWebImageNoParamsBlock)completionBlock {
``` 
  1. 返回数据显示
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];

到此整个图片加载流程就完了.

查找过程

缓存模块

下图是最新的 SDWebImage 的文件结构,这里主要看看 Cache 板块

Cache 相关类截图
首先先看设置文件 SDImageCacheConfig
* 定义了默认的磁盘缓存时间, 默认为 1 周 `kDefaultCacheMaxDiskAge = 60 * 60 * 24 *7;`
* shouldDisableiCloud 是否使用 iCloud
* shouldCacheImagesInMemory 是否使用内存缓存
* shouldUseWeakMemoryCache 是否使用弱应用内存缓存
* shouldRemoveExpiredDataWhenEnterBackground 是否删除超过日期的数据
* maxDiskAge 最大磁盘缓存周期为 `1` 周
* maxDiskSize 最大磁盘缓存大小.默认为 0,意味着没有大小限制
* maxMemoryCost 内存缓存大小,默认为 0,意味着没有大小限制
* maxMemoryCount 内存缓存的最大数量,默认 0
* diskCacheExpireType  磁盘缓存失效的类型(用于删除使用)
SDMemoryCache
* `SDMemoryCache` 是继承自 系统的 `NSCache`.
* 这个类主要是操作内存,删除,保存的操作
* 首先注册了内存警告通知 `UIApplicationDidReceiveMemoryWarningNotification`
* 保存
    - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
        [super setObject:obj forKey:key cost:g];
        if (!self.config.shouldUseWeakMemoryCache) {
            return;
        }
        if (key && obj) {
        // Store weak cache
            SD_LOCK(self.weakCacheLock);
        [self.weakCache setObject:obj forKey:key];
          SD_UNLOCK(self.weakCacheLock);
        }
    }
    
    Note: 这里的保存方法是做了两次保存操作
        1. 使用 [Super setObject:obj forKey:key cost: g] 先给父类设置,也就是给系统的 NSCache 设置缓存
        2. 然后在给当前缓存内的 `NSMapTable` 设置值
        3. 这样也就是拿空间换时间的操作,相当于在内存中是有两份数据的,如果第一份没有,就去拿第二份,这样就保证了快速度的响应. 

* 取值
    -(id)objectForKey:(id)key {
         id obj = [super objectForKey:key];
        if (!self.config.shouldUseWeakMemoryCache) {
              return obj;
        }
          if (key && !obj) {
           // Check weak cache
        SD_LOCK(self.weakCacheLock);
           obj = [self.weakCache objectForKey:key];
       SD_UNLOCK(self.weakCacheLock);
       if (obj) {
          // Sync cache
          NSUInteger cost = 0;
          if ([obj isKindOfClass:[UIImage class]]) {
              cost = [(UIImage *)obj sd_memoryCost];
          }
          [super setObject:obj forKey:key cost:cost];
      }
        }
      return obj;
    }
    1. 先去 `super` 取值
    2. 如果木有就去 `NSMapTable` 中取值
SDDiskCache
* 使用 NSFileManager 管理图片缓存
* 根据 `key` 在它 SDDiskCacheFileNameForKey 方法中,使用 `CC_MD5` 生成一个 新的  `Key`, 转换成 url,存放 data.
* 还有一点就是清理过期的数据,有两种方式
    1. SDImageCacheConfigExpireTypeAccessDate 根据访问时间
    2. SDImageCacheConfigExpireTypeModificationDate 修改时间(默认)
    3. 根据 self.config.maxDiskAge 来对比删除超过时间的图片
    4. 根据 self.config.maxDiskSize 来删除磁盘缓存的数据,清理到self.config.maxDiskSize /2 为止.
SDImageCachesManager
* 主要是操作 `SDImageCache` 类,对缓存就行 `存储`, `删除`, `清理`, `查询`
SDImageCacheConfig
* 主要是对图片的 `Decode` 操作
SDImageCache
* 值得说的是,他在这里监听了两个系统通知
1. UIApplicationWillTerminateNotification
2. UIApplicationDidEnterBackgroundNotification
当这两个方法执行时,其实是做了一件事情,就是清理磁盘缓存 
[self.diskCache removeExpiredData];

下载模块

SDWebImageDownloaderConfig
* 设置最大并发数 maxConcurrentDownloads ,默认是 6
* 设置下载超时时间 downloadTimeout ,默认 15s
* 下载执行顺序 executionOrder , 默认先进先出
SDWebImageDownloader
Note: 总结就是 使用 系统 NSURLSession 创建 task ,在本类中它实现了 NSURLSessionTaskDelegate, 当数据下载完成,就去显示.
    * 主要是设置下载队列
    * 拼接 HTTPHeader 
    * 组装 SDWebImageDownloaderOperation 
    * 设置 Credential
    * 配置 NSOperation 的优先级
    * 返回一个 SDWebImageDownloaderOperation
    * 虽然设置了 NSURLSessionTaskDelegate 的代理在这个类中,但是实际还是把结果交费给了 SDWebImageDownloaderOperation, 统一处理
    
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
            [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
     } else {
      if (completionHandler) {
           completionHandler(NSURLSessionResponseAllow);
         }
    }
SDWebImageDownloaderOperation
Note: 他是集成自 NSOperation, 重写了 Start 方法
    * 在 Start 时,如果 app 进入后台,就取消下载.
    * 设置 NSURLSession 之后就下载任务
    * 其中手动(KVC)出发了 isFinished isExecuting
    * 然后就是在 NSURLSessionTaskDelegate,NSURLSessionDataDelegate,设置显示就回调
上一篇 下一篇

猜你喜欢

热点阅读