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的一个方法返回的。我们猜想对不应该是一些关于缓存的策略。

 

posted @ 2018-12-25 15:11  高山流水觅知音  阅读(223)  评论(0编辑  收藏  举报