SDWebImage375源码-壹

0. 图片的异步下载

比如在tableview中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

static NSString* cellID = @"cellID";

UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"1.jpg"]];
cell.textLabel.text = @" text ";

return cell;

}

他这里相关的代码就是调用了UIImageView+WebCache这个分类中的 sd_setImageWithURL:placeholderImage:方法

1. 开始看代码

1
2
3
4

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

发现原先方法调用了另一个参数更多的方法 sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil

其中url是图片的url地址,placeholderImag是占位图,options是某个选项?,progress是进度completed肯定就是完成后的操作块

问题1 : 其中options:0 这个options值是具体是什么

1.1 SDWebImageOptions具体内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
//默认情况下 url下载失败,url会被移入黑名单
//这个flag将url从黑名单中移除
//简单来说就是失败后重新下载
SDWebImageRetryFailed = 1 << 0,

//默认图片下载在UI交互的时候开始
//延迟下载
SDWebImageLowPriority = 1 << 1,

//只进行内存缓存
SDWebImageCacheMemoryOnly = 1 << 2,

//默认图片只会在完全下载完显示
//这个flag可以使图片渐进式下载,图片也会逐步显示
SDWebImageProgressiveDownload = 1 << 3,

//刷新缓存
SDWebImageRefreshCached = 1 << 4,

//后台下载
SDWebImageContinueInBackground = 1 << 5,

SDWebImageHandleCookies = 1 << 6,

//允许使用无效的SSL证书
SDWebImageAllowInvalidSSLCertificates = 1 << 7,

//有限加载
SDWebImageHighPriority = 1 << 8,

//延迟占位图
SDWebImageDelayPlaceholder = 1 << 9,

SDWebImageTransformAnimatedImage = 1 << 10,

//图片下载后 手动加载图片
SDWebImageAvoidAutoSetImage = 1 << 11
};

这里options使用0,表示不使用任何选项

2. sd_setImageWithURL具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {

//看名字猜测是取消当前图片加载(任务)
[self sd_cancelCurrentImageLoad];
...

//使用placeholder图片先占位
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
...

//这里就应该是主要的方法 下载图片了
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
...
wself.image = image;
...
}];

//看名字猜测将这个图片下载任务以UIImageViewImageLoad作为关键字储存起来
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

...
}

2.1 先看[self sd_cancelCurrentImageLoad]

它是调用了以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}

`sd_cancelImageLoadOperationWithKey`这个方法在 `UIView+WebCacheOperation`这个分类中:

- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
// Cancel in progress downloader from queue

//会将operation 都储存在operationDictionary?
NSMutableDictionary *operationDictionary = [self operationDictionary];

//取出key对应的operation(s) ,并执行cancel操作,然后将operation(s)从operationDictionary中删除
id operations = [operationDictionary objectForKey: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];
}
}

也就是将key为UIImageViewImageLoad的操作都执行cancel操作,最后将key对应的对象全部从operationDictionary中删除

看到这里有几个问题

UIImageViewImageLoad对应的操作是图片正在下载中,还是下载完呢?应该是下载完的操作,因为要是下载中操作都被取消并从operationDictionary中移除,图片也下不成功了。但是要是对应下载完,operation已经被移除,它是依据什么做到同一url不重复下载??url(或者ur对应的特征码)不储存在operation(但是SDWebImageOperation只是一个协议 只声明了cancel操作)中吗?还有operationDictionary的内部储存数据的结构是什么?怎么一个key又可能是数组有可能是单个对象,这样做不是麻烦一点吗?全改成一个key对应一个operation数组不是更方便?所以问题就这几个:

  • 问题2:UIImageViewImageLoad对应的是什么操作

  • 问题3:operation的实现(SDWebImageOperation中的cancel方法实现 和 内部的属性等)

    1
    2
    3
    @protocol SDWebImageOperation <NSObject>
    - (void)cancel;
    @end
  • 问题4:不重复下载相同url 是根据operation做的,还是根据其他对象实现的

  • 问题5:operationDictionary的内部储存数据(能存什么key,key对应的对象是数组还是其他)

    • 目前知道有个key 为UIImageViewImageLoad ,看代码猜测当Operation数量为多个时,key对应的是NSArray<SDWebImageOperation*> 对象,当数量为单个时key对应的是 SDWebImageOperation*对象(或者是考虑到兼容问题?)

感觉这几个问题都可以在下载的具体步骤中解决掉。。

2.2 接下来看 dispatch_main_async_safe

它的实现就是一个宏:

1
2
3
4
5
6
7

#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}

这个我有个蠢问题,为什么要这么做??就算是已经在主线程 但是再执行dispatch_async(dispatch_get_main_queue(), block)也不会错吧??虽然是会感觉多此一举,但是这样做其他的影响呢,会影响性能吗。。

2.3 [self showActivityIndicatorView]

我加上 [cell.imageView setShowActivityIndicatorView:true];也没有看到等待指示器。。是网速太快了还是需要其他设置。。不管了

2.4 [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

看下它的代码:

1
2
3
4
5
6

- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
[self sd_cancelImageLoadOperationWithKey:key];
NSMutableDictionary *operationDictionary = [self operationDictionary];
[operationDictionary setObject:operation forKey:key];
}

它将Operation以key为 UIImageViewImageLoad 存入operationDictionary,而这时Operation对应的下载肯定是异步的,所以UIImageViewImageLoad对应的是图片正在下载中的操作。

  • 回答问题2:UIImageViewImageLoad对应的是图片正在下载中的操作。
  • 回答问题5(部分):operationDictionary中以 key-operation 方式储存(key-nsarray方式暂时没看到)

若是当第一个图片没下载完,第二图片下载任务进来不就取消之前所有的UIImageViewImageLoad的operation了??那之前的图片怎么下载完?难道cancel之后会自动resume吗?

  • 问题6:UIImageViewImageLoad的operation执行 cancel后,在哪里会继续下载?

3 接下来就是内容最多的下载功能了

[SDWebImageManager.sharedManager downloadImageWithURL: l options: progress: completed: