主要围绕三个方面来阐述
- 加载流程
- 缓存模块
- 下载模块
加载流程
这是从 github SDWebImage 地址下载的图. 图片说明了整个图片加载的时序.- 首先是根据地址去缓存中取图片,此处的缓存是
双缓存
(磁盘 + 内存) - 如果找到了就交付到上层处理/显示
- 如果没有都没找到,就会去网络下载图片
- 下载完了交付上层处理/显示
- 另外对下载的图片进行缓存
下面根这源码来验证一下上面说的知识
根据 Github 上面提供的整个 SD 的类图可知,主要是 SDWebImageManager
在操作管理查找流程.
我们就根据大家使用的最多的方法, 来一步步跟踪.
- 通常情况下,我们使用这个方法对图片进行加载。
UIImageView+WebCache.h 文件中
-(void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
- 查找
sd_internalSetImageWithURL
方法.
UIView+WebCache.h 文件中
sd_internalSetImageWithURL ....
- 如果你设置了 PlaceholderImage, 他会先去显示你的 placeholderImage
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
- 继续去 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) {
- 然后调用
callCacheProcessForOperation
进行缓存查找操作
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
// 缓存查找
}else {
// 进行下载操作
}
- 接上缓存查找过程, 回去
SDImageCache
中的queryImageForKey
方法继续缓存查找
-(id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
-
接下来到
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];
-
如果没找到就去下载图片
callDownloadProcessForOperation
- 进入下载流程, 生成
SDWebImageDownloadToken
对象.就行下载
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
- 下载完成就行存储
[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 {
```
- 返回数据显示
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
到此整个图片加载流程就完了.
缓存模块
下图是最新的 SDWebImage 的文件结构,这里主要看看 Cache 板块
- 首先介绍这些文件是干啥的(记得之前的 Cache 没这么多文件)
- SDImageCacheConfig.h SDImageCacheConfig.m 这两个文件是来做一些设置
- SDMemoryCache.h SDMemoryCache.m 是内存缓存相关的操作
- SDDiskCache.h SDDiskCache.m 是磁盘缓存相关的操作
- SDImageCacheDefine.h SDImageCacheDefine.m 主要是为 SDImageCache 定义了一些协议
- SDImageCachesManager.h SDImageCachesManager.m 主要对 Cache 删除和添加
- SDImageCache.h SDImageCache.m 操作
SDDiskCache
和SDMemoryCache
首先先看设置文件 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,设置显示就回调