SDWebImage3.7.5源码阅读二


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

方法在SDWebImageManager.m中

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

参数:

  • url
  • options
    • 之前已经介绍了 SDWebImageOptions
  • progressBlock
    • SDWebImageDownloaderProgressBlock 定义在 SDWebImageDownloader.h 中具体实现为:

        //从名字可以看出来第一个参数是已经接受了数据的大小
        //另一个参数表示总数据的大小
        typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
      
  • completedBlock
    • 图片下载完要做的块 具体实现为:

        typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
      

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

3.1 一些判断

这个没什么好说的

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

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

它的具体实现:

//头
@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

//判断是否是已经下载失败的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实例

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

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

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

来看看它的实现:

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

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

接下来看吧:

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

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

return operation;

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

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

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

接着来看queryDiskCacheForKey:

- (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的单例(流程之外)

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

+ (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使创建出来的对象严格保持单例,结果实验了一下:

    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方法就不会错。。

原文地址:https://www.cnblogs.com/sunyanyan/p/5394811.html