2. operation执行过程
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
/*
* url:下载图片的url
* option:该请求所要遵循的选项
* progressBlock:图片正在下载调用的block
* completedBlock:当操作完成后调用该block这个参数必须存在
*
* 该block没有返回值并且用请求的UIImage作为第一个参数。
* 如果请求出错,那么image参数为nil,而第二参数将包含一个NSError对象。
* typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
SDImageCacheType:图片获取的途径:硬盘、内存、web
* 当options设为SDWebImageProgressiveDownload并且此时图片正在下载,finished将设为NO
* 因此这个block会不停地调用直到图片下载完成,此时才会设置finished为YES.
*
* return:返回一个遵循SDWebImageOperation协议的NSObject.应该是一个SDWebImageDownloaderOperation的实例
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
completedBlock不能为空
NSAssert(completedBlock !=nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
如果入的url类型为string 那么要转变成NSURL类型
if ([urlisKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
防止传入一个NSNull给
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![urlisKindOfClass:NSURL.class]) {
url = nil;
}
创建operation
__blockSDWebImageCombinedOperation *operation = [SDWebImageCombinedOperationnew];
__weak SDWebImageCombinedOperation *weakOperation = operation;
搜索当前的url是否在failedUrs的集合中,这个failedURLs保存
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLscontainsObject:url];
}
如果这个图片url无法下载,那就使用completedBlock进行错误处,图片无法下载的情况有两种:
第一种情况是该url为空,另一种情况就是如果是failedUrl也无法下载,但是要避免无法下载就放入failedUrl的情况,就要设置options为SDWebImageRetryFailed。一般默认image无法下载,这个url就会加入黑名单,但是设置了SDWebImageRetryFailed会禁止添加到黑名单,不停重新下载。
if (url.absoluteString.length ==0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
主线程中进行错误处理
NSError *error = [NSErrorerrorWithDomain:NSURLErrorDomaincode:NSURLErrorFileDoesNotExistuserInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}
如果可以下载就将这个operation加入到数组中(加锁操作)
@synchronized (self.runningOperations) {
[self.runningOperationsaddObject:operation];
}
用图片的url来获取cache对应的key,也就是说cache中如果已经有了该图片,那就返回该图片在cache中对应的key,你可以根据这个key去cache中获取图片
NSString *key = [selfcacheKeyForURL:url];
获取到key通过key来
operation.cacheOperation = [self.imageCachequeryDiskCacheForKey:key done:^(UIImage *image,SDImageCacheType cacheType) {
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperationsremoveObject:operation];
}
return;
}
如果delegate没有实现上面那个函数,整个表达式就为真,相当于该函数返回了YES。如果delegate实现了该函数,那就执行该函数,并且判断该函数执行结果。如果函数返回NO,那么整个if表达式都为NO,那么当图片缓存未命中时,图片下载反而被阻止。后半段目前恒等于YES
if ((!image || options & SDWebImageRefreshCached) && (![self.delegaterespondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegateimageManager:selfshouldDownloadImageForURL:url])) {
如果图片在缓存中找到,但是options中有SDWebImageRefreshCached
那么就尝试重新下载该图片,这样是NSURLCache有机会从服务器端刷新自身缓存。
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
completedBlock(image, nil, cacheType,YES, url);
});
}
// download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions =0;
if (options &SDWebImageLowPriority) downloaderOptions |=SDWebImageDownloaderLowPriority;
if (options &SDWebImageProgressiveDownload) downloaderOptions |=SDWebImageDownloaderProgressiveDownload;
if (options &SDWebImageRefreshCached) downloaderOptions |=SDWebImageDownloaderUseNSURLCache;
if (options &SDWebImageContinueInBackground) downloaderOptions |=SDWebImageDownloaderContinueInBackground;
if (options &SDWebImageHandleCookies) downloaderOptions |=SDWebImageDownloaderHandleCookies;
if (options &SDWebImageAllowInvalidSSLCertificates) downloaderOptions |=SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options &SDWebImageHighPriority) downloaderOptions |=SDWebImageDownloaderHighPriority;
if (image && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
id <SDWebImageOperation> subOperation = [self.imageDownloaderdownloadImageWithURL:url options:downloaderOptionsprogress:progressBlockcompleted:^(UIImage *downloadedImage,NSData *data, NSError *error,BOOLfinished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
}
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error,SDImageCacheTypeNone, finished, url);
}
});
if ( error.code !=NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLsaddObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLsremoveObject:url];
}
}
BOOL cacheOnDisk = !(options &SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
}
else if (downloadedImage && (!downloadedImage.images || (options &SDWebImageTransformAnimatedImage)) && [self.delegaterespondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{
UIImage *transformedImage = [self.delegateimageManager:selftransformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImageisEqual:downloadedImage];
[self.imageCachestoreImage:transformedImagerecalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ?nil : data)forKey:keytoDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil,SDImageCacheTypeNone, finished, url);
}
});
});
}
else {
if (downloadedImage && finished) {
[self.imageCachestoreImage:downloadedImagerecalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil,SDImageCacheTypeNone, finished, url);
}
});
}
}
if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperationsremoveObject:strongOperation];
}
}
}
}];
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperationsremoveObject:strongOperation];
}
}
};
}
else if (image) {
从缓存中获取到了图片,而且不需要刷新缓存的
直接执行completedBlock,其中error置为nil即可。
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType,YES, url);
}
});
执行完后,说明图片获取成功,可以把当前这个operation溢移除了。
@synchronized (self.runningOperations) {
[self.runningOperationsremoveObject:operation];
}
}
else {
// Image not in cache and download disallowed by delegate
又没有从缓存中获取到图片,shouldDownloadImageForURL又返回NO,不允许下载
所以completedBlock中image和error均传入nil
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperationsremoveObject:operation];
}
}
}];
return operation;
}
**********************************************************
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
doneBlock必须实现
if (!doneBlock) {
return nil;
}
如果key为nil,则在内存中没有图片,则之间返并标记为web模式
if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
}
// First check the in-memory cache...
从内存中取图片
UIImage *image = [selfimageFromMemoryCacheForKey:key];
如果能取到图片就返回并标记为从内存中取出的
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
从硬盘中取创建operation
NSOperation *operation = [NSOperationnew];
异步函数处理并发对列
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
从硬盘中取图片
UIImage *diskImage = [selfdiskImageForKey:key];
如果取到图片并且需要缓存到内存中
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCachesetObject:diskImage forKey:key cost:cost];
}
取到图片就返回并标记为从硬盘中取出的
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
return operation;
}
**********************************************************