SDWebImage源码解读(五)SDWebImageManager之.M文件
.M
首先看初始化方法
1 - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { 2 if ((self = [super init])) { 3 _imageCache = cache; 4 _imageDownloader = downloader; 5 _failedURLs = [NSMutableSet new]; 6 _runningOperations = [NSMutableArray new]; 7 } 8 return self; 9 }
这个方法很简单,就是进行一些初始化设置。由于它属于指定初始化方法,所以要调用父类的init方法。方法中初始化了一个存储URL的集合,一个存储操作的数组。
这里我们稍微解释一下"NSMutableSet"这个类,它继承自NSSet,我们称之为集合。NSSet和NSArray都是存储容器,可以用来存储对象的地址。不同的是,NSSet内部是无序的,NSArray是有序的。集合是一种哈希表,运用散列算法,查找集合中元素的速度比数组速度更快。
所以我们可以知道,集合可以用来存储一组不要求顺序的数据,以后我们在coding的时候,也可以使用它,提高代码效率。
接下来,我们看一下这段代码中最长的一个方法,我们来研究这个方法,由于代码太长,这里就不附上代码了,小伙伴们阅读的时候可以打开代码,对照着研究:
1 // Invoking this method without a completedBlock is pointless 2 NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
step1: 这个东西看着眼生,百度了一下,这个东西叫做断言,这是一个宏定义出现在Foundation框架的NSException类中,主要用于开发阶段调试程序中的bug。通过NSAssert()传递条件表达式来判断是否属于bug,满足条件,则程序继续运行,如果不满足,则抛出异常,并且可以自定义异常描述。
那么咱们看到的这个断言,作用就是如果completedBlock是nil,抛出异常,提示“如果你想抓取image,使用[SDWebImagePrefetcher prefetchURLs]”,那么也就是说,completedBlock不能为nil
1 if ([url isKindOfClass:NSString.class]) { 2 url = [NSURL URLWithString:(NSString *)url]; 3 } 4 5 // Prevents app crashing on argument type error like sending NSNull instead of NSURL 6 if (![url isKindOfClass:NSURL.class]) { 7 url = nil; 8 }
step2:两个if判断,排除url是字符串或者nil的可能,确保url的安全。
1 __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; 2 __weak SDWebImageCombinedOperation *weakOperation = operation;
我们看 SDWebImageCombinedOperation 这个声明,他也在本文件中,下面是具体代码:
1 @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation> 2 3 @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; 4 @property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock; 5 @property (strong, nonatomic, nullable) NSOperation *cacheOperation; 6 7 @end
我们可以看到,它有三个属性,遵守SDWebImageOperation协议。
我们再来看具体实现:
1 @implementation SDWebImageCombinedOperation 2 3 - (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock { 4 // check if the operation is already cancelled, then we just call the cancelBlock 5 if (self.isCancelled) { 6 if (cancelBlock) { 7 cancelBlock(); 8 } 9 _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes 10 } else { 11 _cancelBlock = [cancelBlock copy]; 12 } 13 } 14 15 - (void)cancel { 16 self.cancelled = YES; 17 if (self.cacheOperation) { 18 [self.cacheOperation cancel]; 19 self.cacheOperation = nil; 20 } 21 if (self.cancelBlock) { 22 self.cancelBlock(); 23 24 // TODO: this is a temporary fix to #809. 25 // Until we can figure the exact cause of the crash, going with the ivar instead of the setter 26 // self.cancelBlock = nil; 27 _cancelBlock = nil; 28 } 29 }
①重写了cancelBlock属性的set方法,一旦设置了这个取消的block,这个方法就会执行。方法的作用是,check是否这个操作已经被取消了,如果被取消了,就执行这个cancelBlock,然后置为nil,否则会造成循环。如果没有被取消,则给cancelBlock赋值。
②我们看到这个cancel方法,它属于SDWebImageOperation协议,一旦operation调用cancel方法,就会执行这段代码。如果有cacheOperation,则取消这个队列并置nil。接着执行cancelBlock,然后置nil。
这里附上__block和__weak的作用和区别:
1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
3.__block对象可以在block中被重新赋值,__weak不可以。
1 @synchronized (self.failedURLs) { 2 isFailedUrl = [self.failedURLs containsObject:url]; 3 }
@synchronized()为一段代码加锁,作用是避免多个线程同时对一段代码修改,也就是说,其他试图执行该段代码的线程都会被阻塞,直到加锁线程执行完该段代码。这段代码就保证了,只有一个线程可以查询failedURLs数组,从而保证了url查询结果的准确性。
1 if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { 2 [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url]; 3 return operation; 4 }
当url为nil,或者url曾经请求失败过,并且下载策略不是SDWebImageRetryFailed,则completeBlock中抛出异常error,并return。
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
step3:给操作队列加锁
1 NSString *key = [self cacheKeyForURL:url]; 2 3 operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { 4 if (operation.isCancelled) { 5 [self safelyRemoveOperationFromRunning:operation]; 6 return; 7 } 8 9 if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { 10 if (cachedImage && options & SDWebImageRefreshCached) { 11 // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image 12 // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. 13 [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 14 } 15 16 // download if no image or requested to refresh anyway, and download allowed by delegate 17 SDWebImageDownloaderOptions downloaderOptions = 0; 18 if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; 19 if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; 20 if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; 21 if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; 22 if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; 23 if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; 24 if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; 25 if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; 26 27 if (cachedImage && options & SDWebImageRefreshCached) { 28 // force progressive off if image already cached but forced refreshing 29 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; 30 // ignore image read from NSURLCache if image if cached but force refreshing 31 downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; 32 } 33 34 SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { 35 __strong __typeof(weakOperation) strongOperation = weakOperation; 36 if (!strongOperation || strongOperation.isCancelled) { 37 // Do nothing if the operation was cancelled 38 // See #699 for more details 39 // 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 40 } else if (error) { 41 [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url]; 42 43 if ( error.code != NSURLErrorNotConnectedToInternet 44 && error.code != NSURLErrorCancelled 45 && error.code != NSURLErrorTimedOut 46 && error.code != NSURLErrorInternationalRoamingOff 47 && error.code != NSURLErrorDataNotAllowed 48 && error.code != NSURLErrorCannotFindHost 49 && error.code != NSURLErrorCannotConnectToHost) { 50 @synchronized (self.failedURLs) { 51 [self.failedURLs addObject:url]; 52 } 53 } 54 } 55 else { 56 if ((options & SDWebImageRetryFailed)) { 57 @synchronized (self.failedURLs) { 58 [self.failedURLs removeObject:url]; 59 } 60 } 61 62 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); 63 64 if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { 65 // Image refresh hit the NSURLCache cache, do not call the completion block 66 } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { 67 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 68 UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; 69 70 if (transformedImage && finished) { 71 BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; 72 // pass nil if the image was transformed, so we can recalculate the data from the image 73 [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil]; 74 } 75 76 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; 77 }); 78 } else { 79 if (downloadedImage && finished) { 80 [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil]; 81 } 82 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; 83 } 84 } 85 86 if (finished) { 87 [self safelyRemoveOperationFromRunning:strongOperation]; 88 } 89 }]; 90 operation.cancelBlock = ^{ 91 [self.imageDownloader cancel:subOperationToken]; 92 __strong __typeof(weakOperation) strongOperation = weakOperation; 93 [self safelyRemoveOperationFromRunning:strongOperation]; 94 }; 95 } else if (cachedImage) { 96 __strong __typeof(weakOperation) strongOperation = weakOperation; 97 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 98 [self safelyRemoveOperationFromRunning:operation]; 99 } else { 100 // Image not in cache and download disallowed by delegate 101 __strong __typeof(weakOperation) strongOperation = weakOperation; 102 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; 103 [self safelyRemoveOperationFromRunning:operation]; 104 } 105 }];
看到这个block怕不怕?☺
从外部看,是给operation对象NSOperation属性附上值。NSOperation是由SDWebImageCache的一个方法返回的。我们猜想对不应该是一些关于缓存的策略。