SDWebImage375源码-贰

3. downloadImageWithURL下载方法的具体实现

方法在SDWebImageManager.m中

1
2
3
4
id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url 	
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock

参数:

  • url
  • options
    • 之前已经介绍了 SDWebImageOptions
  • progressBlock

    • SDWebImageDownloaderProgressBlock 定义在 SDWebImageDownloader.h 中具体实现为:
      1
      2
      3
      //从名字可以看出来第一个参数是已经接受了数据的大小
      //另一个参数表示总数据的大小
      typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
  • completedBlock

    • 图片下载完要做的块 具体实现为:
      1
      2
      		
      typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);

然后一看这个方法实现……麻蛋 好长!

3.1 一些判断

这个没什么好说的

1
2
3
4
5
6
7
8
9
10
 
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}

if (![url isKindOfClass:NSURL.class]) {
url = nil;
}

3.2 SDWebImageCombinedOperation

下来看到了一个类 SDWebImageCombinedOperation

1
2
3

__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

它的具体实现:

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

//头
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end

//实现
@implementation SDWebImageCombinedOperation

- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {

if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil;
} else {
_cancelBlock = [cancelBlock copy];
}
}

//cacheOperation 对应的到底是 下载操作还是 缓存相关的操作。。
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();

_cancelBlock = nil;
}
}

@end

它实现了 SDWebImageOperation好歹是回答了之前的问题3

  • 回答问题3(部分):SDWebImageOperation的实现之一是SDWebImageCombinedOperation

但是这个cacheOperation 命名令我很困惑。。因为到现在还不知道下载操作会放在那里。。

  • 问题7 : SDWebImageCombinedOperation的cacheOperation执行什么操作

然后它的属性cancelBlock是 长这样的 typedef void(^SDWebImageNoParamsBlock)();好像没什么用的样子。。

3.3 isFailedUrl

1
2
3
4
5
6
7
8
9
10
11
12
13
14

//判断是否是已经下载失败的url
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
//当url是失败过的url并且options不是SDWebImageRetryFailed 时直接报错
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}

这里 终于解决了之前的第四个问题,failedURLs就储存了失效的url,也就是依据这个做出不重复下载的功能

  • 回答问题4: 不重复下载相同url 是根据SDWebImageManager.failedURLs来实现的

看到这里 还没有看见下载的功能。。判断了这么多条件 真是值得学习啊。。

3.4 储存operation,生成cacheOperation实例

1
2
3
4
5
6

//将之前生成的 SDWebImageCombinedOperation *operation
//存入 runningOperations
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}

之前不是猜测 SDWebImageCombinedOperation中的 cacheOperation是缓存还是下载的操作吗,到这里就可以猜出来,应该是缓存和下载操作都有,因为要是只是缓存操作的话,这个操作不会进行很久,一般也不需要储存起来管理。

1
2
3

//应该是生成图片缓存路径对应的key
NSString *key = [self cacheKeyForURL:url];

来看看它的实现:

1
2
3
4
5
6
7
8
9

- (NSString *)cacheKeyForURL:(NSURL *)url {
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
}
else {
return [url absoluteString];
}
}

其中 cacheKeyFilter是过滤url用的,它是个SDWebImageCacheKeyFilterBlock块,作者注解中写的很清楚了,它可以用来删除url中动态生成的部分,比如一些“?”之后的参数什么的,但是我没用到,这里就不讨论如何自定义这个SDWebImageCacheKeyFilterBlock块了。

接下来看吧:

1
2
3
4
5
6
7
8

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {

...
//一大串 块内容,
}];

return operation;

这里终于要开始实现cacheoperation了。。

其中self.imageCache 是在init方法中生成的 就是生成一个SDImageCache的单例:

1
2
3
4

- (SDImageCache *)createCache {
return [SDImageCache sharedImageCache];
}

接着来看queryDiskCacheForKey:

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

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
...

//在内存中查看是否存在
//其实就是在NSCache类的一个memCache对象中查找
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}

NSOperation *operation = [NSOperation new];
//ioQueue就是SDImageCache初始化时生成一个GCD并行队列
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
//在本地中查看图片是否存在
@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}

dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});

return operation;
}

我这流程中 图片是第一次下载的,所以就按着 内存和本地中都找不到流程走。

看到这里,有点奇怪为什么这里要加autorealeasepool,看了下资料,说是可以优化内存。

autorealeasepool机制参考链接

autorealeasepool机制参考链接2

3.5 关于SDWebImageManager的单例(流程之外)

一开始看见它的单例是这么写,这不一看就知道不是严格的单例吗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

+ (id)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}

- (id)init {
if ((self = [super init])) {
...
}
return self;
}

一开始我还以为是用了什么高级的Runtime使创建出来的对象严格保持单例,结果实验了一下:

1
2
3
4
5
6

SDWebImageManager* m1 = [SDWebImageManager sharedManager];
NSLog(@" m1 :%@ ",m1);

SDWebImageManager* m2 = [[SDWebImageManager alloc]init];
NSLog(@" m2 :%@ ",m2);

打印出来:

sdwebImageTest[4350:1125408]  m1 :<SDWebImageManager: 0x17550510>  
 sdwebImageTest[4350:1125408]  m2 :<SDWebImageManager: 0x175537e0>

摔!这不就是《只要你确保只调用sharedManager就确保单例》的做法吗。。作者开心就好。。反正里面的单例模式大家都只调用默认的sharedManager方法就不会错。。