SDWebImage 4.x版本源码分析(六)问题总结
问题总结:
1、为什么设置图片前要取消当前的加载任务?
2、dispatch_group_t 在 UIView+WebCache 里的作用是什么?
3、weak-strong dance的用途?
4、#pragma clang diagnostic push
#pragma clang diagnostic ignored "-W相关命令”
//
#pragma clang diagnostic pop 的作用是什么?
5、如何实现取消下载图片任务的?
一、为什么设置图片前要取消当前的加载任务?
如果不取消,那么当tableView滑动的时候,当前cell的imageView会一直去下载图片,然后优先显示下载完成的图片,导致图片错乱。
二、dispatch_group_t 在 UIView+WebCache 里的作用是什么?
1.在FLAnimatedImageView(WebCache)中创建了线程组dispatch_group_t,然后通过key为SDWebImageInternalSetImageGroupKey的字典传入UIView(WebCache)中
— 如果是展示占位图
2.在UIView(WebCache)中设置占位图时,首先获取到是否存在SetImageGroupKey,也就是说是否是FLAnimatedImageView,是就获取到FLAnimatedImageView(WebCache)中传来的group,先enter,然后调用setImageBlock中的方法。
3.在setImageBlock中设置self.image,然后leave。
— 通过url加载到图片后
4.通过url加载到图片后,拿到group,先enter,然后执行setImageBlock。若此时的图片是GIF类型的,首先从GIF图中取出第一张,设为静态海报图像,避免闪烁。其次在全局队列中异步创建FLAnimatedImage,然后在主线程中设置self.animatedImage,并leave group。
5.notify中的block,也就是当group中的所有任务都完成后,调用下[self setNeedsLayout]; - setNeedsLayout 的作用是标记,标记为需要重新布局,异步调用layoutIfNeeds 刷新布局,不利己刷新,但layoutSubviews一定会被调用。
三、weak-strong dance的用途
通过__weak来避免循环引用,weakSelf是附有__weak修饰符的变量,它并不会持有对象,一旦它指向的对象被废弃了,它将自动被赋值为nil。
在多线程情况下,可能weakSelf指向的对象会在 Block 执行前被废弃。
通过__strong来持有weakSelf指向的对象,保证在执行 Block 期间该对象不会被废弃。
四、
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-W相关命令”
//
#pragma clang diagnostic pop 的作用是什么?
忽略clang编译器警告,如下面的代码如果不写会提示 Undeclared selector ‘dynamicSelector’,
//#pragma clang diagnostic push
//#pragma clang diagnostic ignored "-Wundeclared-selector"
[self performSelector:@selector(dynamicSelector) withObject:nil];
//#pragma clang diagnostic pop
五、如何实现取消下载图片任务的?
1.在UIView(WebCache)里,首先就调用了取消当前的加载任务。
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key
{
// 从队列中取消进度下载
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
@synchronized (self) {
// 根据key取出当前的操作 (这里取出的operation是实现了SDWebImageOperation协议的 SDWebImageCombinedOperation)
operation = [operationDictionary objectForKey:key];
}
if (operation) {
//检查是否实现了SDWebImageOperation协议的方法
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]){
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
这里做了两件事,
1.从operationDictionary中取出operation(实际上就是SDWebImageCombinedOperation),
2.判断operation是否实现了SDWebImageOperation协议(SDWebImageCombinedOperation实现了这个协议),调用取消方法,并从operationDictionary中移除key。
2.SDWebImageCombinedOperation里实现了cancel
@implementation SDWebImageCombinedOperation
- (void)cancel {
@synchronized(self) {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel ];
self.cacheOperation = nil;
}
if (self.downloadToken) {
[self.manager.imageDownloader cancel:self.downloadToken];
}
[self.manager safelyRemoveOperationFromRunning:self];
}
}
<1.cancel掉self.cacheOperation <NSOperation>
<2.manager的imageDownloader cancel掉 self.downloadToken
<3.manager.runningOperation中删掉self
其中第<2点,调用的SDWebImageDownloader 的-cancel:方法
3.SDWebImageDownloader的-cancel:方法
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
NSURL *url = token.url;
if (!url) {
return;
}
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (operation) {
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:url];
}
}
UNLOCK(self.operationsLock);
}
<<1.从manager的URLOperations中找到 SDWebImageDownloaderOperation < SDWebImageDownloaderOperationInterface,SDWebImageOperation>
<<2.operation 调用cancel token.downloadOperationCancelToken (这个token.downloadOperationCancelToken 就是回调的字典)
<<3.如果需要取消,从URLOperation中删掉url
其中<<2调用的是SDWebImageDownloaderOperation的-cancel:方法
4.SDWebImageDownloaderOperation 删除保存回调的字典
- (BOOL)cancel:(nullable id)token {
BOOL shouldCancel = NO;
//同步 阻塞当前队列和当前线程(用的信号量)
LOCK(self.callbacksLock);
//删除数组中回调块数组中的token对象,token是key为string,value是block的字典
//removeObjectIdenticalTo 删掉地址该token的地址,而不是值。
[self.callbackBlocks removeObjectIdenticalTo:token];
//判断数组是否为0.则取消下载任务
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
UNLOCK(self.callbacksLock);
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
<<<1.从callbackBlocks中删掉token(删掉回调的字典)
<<<2.如果Blocks数组为0,取消下载任务,shouldCancel = YES;
<<<3.如果Blocks数组为0,调用 [self cancel];
<<<4.返回shouldCancel
对于<<<3中的[self cancel]; 实际上调用的是SDWebImageDownloaderOperation的cancel方法
5.SDWebImageDownloaderOperation 取消下载任务
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
//如果下载图片的任务仍在 则立即取消cancel,并且发送结束下载的通知
if (self.dataTask) {
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
// As we cancelled the task, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
<<<<1.super cancel
<<<<2.如果还有任务,NSURLSessionTask cancel
<<<<3.通知结束下载
这样就完成了取消下载任务。