写在最前面
大家都很忙, 如果你没时间看下面一长串的文字, 这里我尽量长话短说, 希望能节约一些你的时间
前提知识
1 每一个UIView+WebCache 都有一个operationDictionary 里面装的是 @{你当前的类名 : Operation}.
2 Operation直接影响图片能否被显示.每次有新的加载任务时都会检查这个字典, key相同则移除Operation, 再添加新的Operation, 下载完成后会移除Operation.
下面以tableViewcell 为例
1 tableViewCell第一次出现,以UIImageView为例 operationDictionary里装的是 @{@”UIImageView” : 第一张图的Operation}
2 这时候tableView上下滑动, cell复用, UIImageView去加载第二张图片(这就开始复用了),
3 这时候检查operationDictionary, 如果第一张图片还没有完成回调, key一定还存在operationDictionary中. 如果key重复了, 则移除第一张图的Operation, 并添加第二张图的Operation 即@{@”UIImageView” : 第二张图的Operation}.
4 候第一张图片下载完成, 准备回调时, 发现第一张图的Operation已经没有了, 就不会回调.
5 第二张图下载完成后, 会检查第二张图片的Operation有没有被第三张, 第四张…..移除.
6 如果图片对应的Operation存在 就调用回调方法,显示图片, 否则不调用回调, 也就不会显示.
由此 SDWebImage 就做到了 避免复用.
注意点
1. 我说的第一张图片,第二张图片,指的是同一个UIImageView要复用的图片, 不是tableView 上显示的第一张,第二张图片.
2. UIView+WebCache 是一个分类, 每一个对象都有自己的 operationDictionary.
当然如果你想具体了解, 可以往下看.
前言:
SDWebImage是iOS开发最常见的开源框架之一, 对SDWebImage的讲解网上非常多, 我在这里就不放传送门了。 在tableView上使用SDWebImage加载图片时能很好的避免复用问题, 那么他是怎么做到的呢? 自己研究了一下, 下面与大家分享。
前提
在看下面之前, 我认为大家已经看过SDWebimage源码, 这里只会对避免复用这块逻辑讲解, 不对其他逻辑做太多解释。 如果看官还没有看过源码, 建议先上网搜下详解, 以免一会有不适感。
正文
第一个问题, tableViewCell 上下滚动, ImageView被复用了但是之前加载的图片没有显示出来, 说明了什么?
对的, 说明之前的操作被取消了对吗? 那么怎么被取消的呢? 第一反应就是回调的block被终止了, 对吗? 我跟了一下源码发现取消操作是在SDWebImageManager 里取消的. 请看下图.
经过我的打印发现, if (!strongOperation || strongOperation.isCancelled) 这个判断能进去的原因是strongOperation 是nil
2. 那么下一个问题strongOperation 在什么时候被置空的?
如果知道了这个问题, 那么为什么imageView复用为什么不会显示错误的图片的问题 我们就弄清楚了.
先看下图
简单说下 UIView+WebCache 调用 SDWebImageManager的方法, 在SDWebImageManager方法里 创建OPeration. Operation去了两个地方, 1. 返回给了 UIView+WebCache. 2. 被抓取到 SDWebImageManager方法内部的Block里了(也就是刚才看到的 取消操作的地方).
好的下面我们看看 UIView+WebCache. 里做了什么 会让strongOperation 置nil.
2 UIView+WebCache
请看下图
关注一下 划线的两个方法
先讲第二个那个方法
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
这个方法的调用,则是把strongOperation 添加到字典里的方法.
查看源码我们可以看到
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
// 取消已经存在的key, Operation
// 重新加入到 Operation字典中
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
将operation 添加到operationDictionary字典中
再说第一个那个划线的方法.
// 如果Operation 字典里 有这个 Operation 则取消 说明这个view 有新的下载任务了.
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
1
2
这个方法的调用就是我们要找的把strongOperation 置nil的地方. 每次有图片需要加载的时候, 都会走这里, 这个方法会检查根据key能不能取到值,如果能取到, 这将这个 Operation 调用取消方法, 且将他中字典里移除.
查看源码
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
// 如果有 当前key 有所对应的 Operation. 则将Operation 取消.并且将key -value 从 OperationDictiona中移除.
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
// 检查 代理是否 被实现.
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}else{
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这段代码其实就是, 在operationDictionary里根据key查找, 如果有则取消, 并且从operationDictionary里面移除.也是导致 strongOperation 是nil的直接原因.
好了, 这就是strongOperation 被置成nil的原因
下面我们关注 这个字典里的key, 因为 strongOperation 是根据key找到到.
4 key
我们先来看下 key是怎么创建的
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
1
2
如果我们使用myimageView sd_setImageWithUR:xxxx 这种方式 operationKey是nil, 那么 key 是通过NSStringFromClass([self class]) 创建的.以UIImageView为例 key 就是 @”UIImageView”.
让我们串起来讲一下
每次有新的加载任务会做两件事
1 检查operationDictionary有没有相同的key 有则将key对应的Operation 取消并删除,
2 将新任务的Operation添加到字典中. 加载图片完成后, 回调时会先检查任务的Operation还在不在, 不在了 则不回调显示, 反之回调显示并移除Operation.
好了, 这就是我理解的SDWebImage 避免复用的方法.
注意点 1. UIView+WebCache是一个分类, 每一个UIImageView都有一个operationDictionary 这里面key发生重复的原因只有一个,就是这个容器上一个加载任务还没完成, 新的加载任务又开始了.这是我一开始没转过弯 理解上卡主的地方.