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