SDWebImage的简单流程图:
上图大致流程是对的,有几个没写到的地方:
- 首先判断url的类型是不是URL类型或string类型,判断url是否为nil
- 占位图更早一些,在url判断后,就行显示占位图
- 加载沙盒中对应的图片后,不仅要显示,而且要把图片缓存到内存中
- 下载完毕后,有一个异步解码的过程,没体现出来
- 如果url正在下载中,则将这个url的下载回调加入到回调数组里面,当相同url下载完毕后,遍历数组,执行所有下载回调完成操作
网上有大佬做了这个图,供参考:
图片来源:SDWebImage源码解析(一)
源码看了一遍,写的很好,具体源码分析就不写了,后面会列出一些写的源码不错的文章。
这篇文章主要来解决两个问题:
问题一:
tableView多个cell同时请求,cell图片下载后显示错位问题
在tableView下,假设20个cell,每个cell都有图片下载操作,然后在下载的同时,下滑、上拉tableView,SDWebImage怎样确保下载的图片不会错位?
假如,tableView的cell没有重用机制,那么每个cell上的图片都是单独请求,即使上拉、下滑,cell的图片依然存在,并且图片下载完毕后,调用回调,将正确显示到imageView上
会出现错位的原因,是因为:某个cell1,已经不在屏幕上(虽然不在屏幕上,但是下载操作还在进行),放在了缓存池,被赋值了新的cell8,cell8有自己的url下载操作,这时,可能会将cell1下载的图片显示到cell8上。然后cell8的图片下载完后,再重新覆盖cell8
cell1与cell8是同一个对象,只是里面的属性值不一样
SDWebImage的处理方法是:
只要调用sd_setImageWithURL
方法,则取消之前的所有下载操作
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self,就是调用下载图片的view,比如cell8上的UIImageView,每个cell上的UIImageView不同,因此,不需要担起其他cell上的UIImageView
虽然validOperationKey
的值是NSStringFromClass([self class]
,也就是UIImageView
但是,调用者对象cell上的UIImageView,每个cell上都不一样,因此,不需要担心key一样的问题
这样,就确保cell8下载图片的时候,cell1的下载图片操作被停止,当cell8下载完毕后,显示的是cell8的图片
问题二:
多个同一个url请求,如何确保回调到正确的位置?
在一个tableView上,有20个cell都是请求同一个图片地址,且图片比较大,耗时20ms
此时,cell1去请求下载操作,处于正在下载中
而cell2去请求下载,发现已经有cell下载了,那么,cell1上的图片下载完毕后,如何让cell2知道下载完毕并显示呢?
SDWebImage
库的设计考虑到了这样的情况,它可以很好地处理这种多个cell需要请求同一个URL的图片的情况。
SDWebImage
底层通过使用一个NSOperationQueue
来控制图片下载任务。当一个UIImage对象调用sd_setImageWithURL:
方法时,SDWebImage
会先在内存缓存中查找相应的图片,如果找不到,则在NSOperationQueue
中增加一个新的下载操作。
由于SDWebImage
内部使用了一个以URL为key的下载操作字典,对于相同URL的请求,它们其实只创建了一个下载操作,其他的cell都会加入到这个下载操作的完成回调列表中。 即,把这个新请求的完成处理回调添加到已有下载操作的回调列表中。
所以当cell1上的图片下载完成后,cell2会通过回调知道图片已经下载完毕,并更新图片显示。
在 SDWebImage 的实现中,每个下载操作都会关联一个回调块(completion block)。当图片下载完成后,SDWebImage 会调用这个回调块,并将下载好的图片传递给回调块。在回调块中,SDWebImage 会遍历所有需要该图片的 cell,并调用它们的 sd_setImageWithURL:placeholderImage:options:
方法来更新图片显示。
下面是一个简化的示例代码,展示了 SDWebImage 的内部实现逻辑:
// SDWebImage 内部实现
- (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *image))completion {
// 检查内存缓存和磁盘缓存
UIImage *cachedImage = [self.imageCache imageFromCacheForKey:url.absoluteString];
if (cachedImage) {
// 如果缓存中有图片,直接调用回调块并返回
completion(cachedImage);
return;
}
// 检查是否已经有下载操作正在进行中
if ([self.downloadOperationDictionary objectForKey:url.absoluteString]) {
// 如果已经有下载操作,将回调块添加到回调队列中
[self.callbackDictionary[url.absoluteString] addObject:completion];
return;
}
// 创建新的下载操作
SDWebImageDownloaderOperation *operation = [SDWebImageDownloaderOperation new];
[self.downloadOperationDictionary setObject:operation forKey:url.absoluteString];
[self.callbackDictionary setObject:@[completion] forKey:url.absoluteString];
// 开始下载图片
[operation startWithURL:url completion:^(UIImage *image) {
// 图片下载完成后的回调
[self.imageCache storeImage:image forKey:url.absoluteString];
// 调用所有关联的回调块
for (void (^callback)(UIImage *) in self.callbackDictionary[url.absoluteString]) {
callback(image);
}
// 清理下载操作和回调队列
[self.downloadOperationDictionary removeObjectForKey:url.absoluteString];
[self.callbackDictionary removeObjectForKey:url.absoluteString];
}];
}
在上述代码中,当 cell1 请求下载图片时,SDWebImage 会创建一个新的下载操作,并将 cell1 的回调块添加到回调队列中。当 cell2 请求下载同一张图片时,SDWebImage 会发现已经有一个下载操作正在进行中,因此将 cell2 的回调块也添加到回调队列中。
当图片下载完成后,SDWebImage 会调用回调队列中的所有回调块,将下载好的图片传递给它们。这样,无论是 cell1 还是 cell2,都会在图片下载完成后收到通知并更新图片显示。
通过这种方式,SDWebImage 可以自动处理多个 cell 请求同一张图片的情况,避免重复下载,并确保所有需要该图片的 cell 都能及时更新显示。
源码论证:
如果operation已经存在,则shouldNotReuseOperation = NO
shouldNotReuseOperation = NO
的话,直接走:
如果已经有下载操作,将回调块添加到回调队列中
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageDownloaderOperationToken *> *callbackTokens;
callbackTokens是一个数组,里面保存着下载回调
图片下载完毕后,遍历tokens,取出里面的token,然后执行token的complete方法
SDWebImageView源码解析资料: