SDWebImage源码阅读(十)SDWebImageManager
SDWebImageManager 是整个 SDWebImage 里面最核心的类之一。
SDWebImageOptions
1 typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { 2 /** 3 * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying. 4 * This flag disable this blacklisting. 5 */ 6 SDWebImageRetryFailed = 1 << 0, 7 8 /** 9 * By default, image downloads are started during UI interactions, this flags disable this feature, 10 * leading to delayed download on UIScrollView deceleration for instance. 11 */ 12 SDWebImageLowPriority = 1 << 1, 13 14 /** 15 * This flag disables on-disk caching 16 */ 17 SDWebImageCacheMemoryOnly = 1 << 2, 18 19 /** 20 * This flag enables progressive download, the image is displayed progressively during download as a browser would do. 21 * By default, the image is only displayed once completely downloaded. 22 */ 23 SDWebImageProgressiveDownload = 1 << 3, 24 25 /** 26 * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed. 27 * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation. 28 * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics. 29 * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image. 30 * 31 * Use this flag only if you can't make your URLs static with embedded cache busting parameter. 32 */ 33 SDWebImageRefreshCached = 1 << 4, 34 35 /** 36 * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for 37 * extra time in background to let the request finish. If the background task expires the operation will be cancelled. 38 */ 39 SDWebImageContinueInBackground = 1 << 5, 40 41 /** 42 * Handles cookies stored in NSHTTPCookieStore by setting 43 * NSMutableURLRequest.HTTPShouldHandleCookies = YES; 44 */ 45 SDWebImageHandleCookies = 1 << 6, 46 47 /** 48 * Enable to allow untrusted SSL certificates. 49 * Useful for testing purposes. Use with caution in production. 50 */ 51 SDWebImageAllowInvalidSSLCertificates = 1 << 7, 52 53 /** 54 * By default, images are loaded in the order in which they were queued. This flag moves them to 55 * the front of the queue. 56 */ 57 SDWebImageHighPriority = 1 << 8, 58 59 /** 60 * By default, placeholder images are loaded while the image is loading. This flag will delay the loading 61 * of the placeholder image until after the image has finished loading. 62 */ 63 SDWebImageDelayPlaceholder = 1 << 9, 64 65 /** 66 * We usually don't call transformDownloadedImage delegate method on animated images, 67 * as most transformation code would mangle it. 68 * Use this flag to transform them anyway. 69 */ 70 SDWebImageTransformAnimatedImage = 1 << 10, 71 72 /** 73 * By default, image is added to the imageView after download. But in some cases, we want to 74 * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance) 75 * Use this flag if you want to manually set the image in the completion when success 76 */ 77 SDWebImageAvoidAutoSetImage = 1 << 11, 78 79 /** 80 * By default, images are decoded respecting their original size. On iOS, this flag will scale down the 81 * images to a size compatible with the constrained memory of devices. 82 * If `SDWebImageProgressiveDownload` flag is set the scale down is deactivated. 83 */ 84 SDWebImageScaleDownLargeImages = 1 << 12 85 };
这是一个 NS_OPTIONS 的枚举,表示下载的选项。
SDWebImageRetryFailed 默认情况下,每一个图片下载都有一个 URL,如果这个 URL 是错误的或者这个 URL 无法下载的时候,这个 URL 会被列入黑名单,并且黑名单中的 URL 是不会再次进行下载的,但是当设置了这个选项后,这个 URL 会被从黑名单中移除,重新下载该 URL 下的图片。
SDWebImageLowPriority 默认情况下,图片下载在 UI 交互期间开始,该选项会禁止该功能,导致延迟下载,例如:UIScrollView 减速。一般来说,下载都是按照一定的先后顺序开始的,就是该选项能够延迟下载,也就是说他的权限比较低,权限比他高的在他前面下载。
SDWebImageCacheMemoryOnly 该选项表示只是把图片缓存到内存中,不再缓存到磁盘中了。
SDWebImageProgressiveDownload 该选项会使图像按进度下载,在下载过程中图片会逐步显示,默认情况下,图片是在下载完毕后仅仅显示一次的。
SDWebImageRefreshCached 有这么一个使用场景,如果一个图片的资源发生了改变,但是该图片的 URL 没有改变,就可以使用这个选项来刷新数据。
SDWebImageContinueInBackground 在应用程序进入后台后继续下载。这是通过询问系统来实现的,iOS 8 以后系统可以给 3 分钟的时间继续后台任务,后台时间到了,如果操作没有完成,也会取消操作。
SDWebImageHandleCookies 使用 Cookies。
SDWebImageAllowInvalidSSLCertificates 允许使用不信任的 SSL 证书,测试模式使用,谨慎在生产模式使用。
SDWebImageHighPriority 默认情况下,图像下载会按他们在队列中的顺序进行,该选项表示把该下载操作移到队列的前面。提高其下载优先级的权限。
SDWebImageDelayPlaceholder 默认情况下,placeholder Image 会在图片下载完成前显示,该选项将设置 placeholder Image 在下载完成之后才显示。
SDWebImageTransformAnimatedImage 使用该选项来自由的改变图片,但是需要使用 transformDownloadedImage delegate。
SDWebImageAvoidAutoSetImage 该选项允许我们在图片下载完成后不会立刻给 UIImageView 设置图片,比较常用的场景是给赋值的图片添加动画。
SDWebImageScaleDownLargeImage 压缩大图片。
命名 Block
1 typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); 2 3 typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL); 4 5 typedef NSString * _Nullable (^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable url);
SDWebImageManagerDelegate
1 @protocol SDWebImageManagerDelegate <NSObject> 2 3 @optional 4 5 /** 6 * Controls which image should be downloaded when the image is not found in the cache. 7 * 8 * @param imageManager The current `SDWebImageManager` 9 * @param imageURL The url of the image to be downloaded 10 * 11 * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. 12 */ 13 - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL; 14 15 /** 16 * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory. 17 * NOTE: This method is called from a global queue in order to not to block the main thread. 18 * 19 * @param imageManager The current `SDWebImageManager` 20 * @param image The image to transform 21 * @param imageURL The url of the image to transform 22 * 23 * @return The transformed image object. 24 */ 25 - (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL; 26 27 @end
两个可选择实现的代理方法:
第一个代理方法:
控件在缓存中未找到图像时应下载哪个图像。
第二个代理方法:
允许在下载后立即转换图像,然后在磁盘和内存上缓存图像。
注意:此方法从全局队列调用,以便不阻碍主线程。
SDWebImageManager 属性
1 @property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate; 2 3 @property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache; 4 @property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader; 5 6 /** 7 * The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can 8 * be used to remove dynamic part of an image URL. 9 * 10 * The following example sets a filter in the application delegate that will remove any query-string from the 11 * URL before to use it as a cache key: 12 * 13 * @code 14 15 [[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) { 16 url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path]; 17 return [url absoluteString]; 18 }]; 19 20 * @endcode 21 */ 22 @property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
SDWebImageManager 方法
1 /** 2 * Returns global SDWebImageManager instance. 3 * 4 * @return SDWebImageManager shared instance 5 */ 6 + (nonnull instancetype)sharedManager;
单例方法。
1 /** 2 * Allows to specify instance of cache and image downloader used with image manager. 3 * @return new instance of `SDWebImageManager` with specified cache and downloader. 4 */ 5 - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader;
使用 SDImageCache 和 SDWebImageDownloader 初始化 SDWebImageManager 实例。
1 /** 2 * Downloads the image at the given URL if not present in cache or return the cached version otherwise. 3 * 4 * @param url The URL to the image 5 * @param options A mask to specify options to use for this request 6 * @param progressBlock A block called while image is downloading 7 * @note the progress block is executed on a background queue 8 * @param completedBlock A block called when operation has been completed. 9 * 10 * This parameter is required. 11 * 12 * This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter. 13 * In case of error the image parameter is nil and the third parameter may contain an NSError. 14 * 15 * The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache 16 * or from the memory cache or from the network. 17 * 18 * The fith parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is 19 * downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the 20 * block is called a last time with the full image and the last parameter set to YES. 21 * 22 * The last parameter is the original image URL 23 * 24 * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation 25 */ 26 - (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url 27 options:(SDWebImageOptions)options 28 progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock 29 completed:(nullable SDInternalCompletionBlock)completedBlock;
如果在缓存中不存在,则在给定的 URL 处下载图像,否则返回缓存版本。
1 /** 2 * Saves image to cache for given URL 3 * 4 * @param image The image to cache 5 * @param url The URL to the image 6 * 7 */ 8 9 - (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
根据 URL 把 image 存入缓存。
1 /** 2 * Cancel all current operations 3 */ 4 - (void)cancelAll;
取消所有当前的操作。
1 /** 2 * Check one or more operations running 3 */ 4 - (BOOL)isRunning;
检查一个或多个操作是否在执行。
1 /** 2 * Async check if image has already been cached 3 * 4 * @param url image url 5 * @param completionBlock the block to be executed when the check is finished 6 * 7 * @note the completion block is always executed on the main queue 8 */ 9 - (void)cachedImageExistsForURL:(nullable NSURL *)url 10 completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
异步检查图像被缓存。
注意:完成的 block 总是在主线程中执行。
1 /** 2 * Async check if image has already been cached on disk only 3 * 4 * @param url image url 5 * @param completionBlock the block to be executed when the check is finished 6 * 7 * @note the completion block is always executed on the main queue 8 */ 9 - (void)diskImageExistsForURL:(nullable NSURL *)url 10 completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
异步检查图像仅仅被缓存在磁盘中。
注意:完成的 block 总是在主线程中执行。
1 /** 2 *Return the cache key for a given URL 3 */ 4 - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
返回指定的 URL 的缓存 key。
下面开始看 SDWebImageManager.m 的实现。
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
SDWebImageCombinedOperation 是对每一个下载任务的封装,重要的是它提供了一个取消功能。
SDWebImageManager
属性:
1 @interface SDWebImageManager () 2 3 @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; 4 @property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader; 5 @property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs; 6 @property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations; 7 8 @end
单例 和 初始化
1 + (nonnull instancetype)sharedManager { 2 static dispatch_once_t once; 3 static id instance; 4 dispatch_once(&once, ^{ 5 instance = [self new]; 6 }); 7 return instance; 8 } 9 10 - (nonnull instancetype)init { 11 SDImageCache *cache = [SDImageCache sharedImageCache]; 12 SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; 13 return [self initWithCache:cache downloader:downloader]; 14 } 15 16 - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { 17 if ((self = [super init])) { 18 _imageCache = cache; 19 _imageDownloader = downloader; 20 _failedURLs = [NSMutableSet new]; 21 _runningOperations = [NSMutableArray new]; 22 } 23 return self; 24 }
_failedURLs 是一个黑名单,存放下载失败的 URL。
指定 URL 的缓存 key
1 - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url { 2 if (!url) { 3 return @""; 4 } 5 6 if (self.cacheKeyFilter) { 7 return self.cacheKeyFilter(url); 8 } else { 9 return url.absoluteString; 10 } 11 }
self.cacheKeyFilter 是一个 block。
检查图片是否已经缓存
1 - (void)cachedImageExistsForURL:(nullable NSURL *)url 2 completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { 3 NSString *key = [self cacheKeyForURL:url]; 4 5 BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil); 6 7 if (isInMemoryCache) { 8 // making sure we call the completion block on the main queue 9 dispatch_async(dispatch_get_main_queue(), ^{ 10 if (completionBlock) { 11 completionBlock(YES); 12 } 13 }); 14 return; 15 } 16 17 [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { 18 // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch 19 if (completionBlock) { 20 completionBlock(isInDiskCache); 21 } 22 }]; 23 }
先获取图片的缓存 key。
self.imageCache 表示内存,去内存中检查图片是否存在,如果存在异步调取主线程执行 completionBlock(YES)。
如果内存中不存在,则再去磁盘中检查,这个完成的 block 仍然是在主线程中执行的,上面的 completion 是被调取到主线程中执行的。
检查磁盘中是否有指定的图片
1 - (void)diskImageExistsForURL:(nullable NSURL *)url 2 completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { 3 NSString *key = [self cacheKeyForURL:url]; 4 5 [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { 6 // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch 7 if (completionBlock) { 8 completionBlock(isInDiskCache); 9 } 10 }]; 11 }
下载方法
1 - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url 2 options:(SDWebImageOptions)options 3 progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock 4 completed:(nullable SDInternalCompletionBlock)completedBlock { 5 // Invoking this method without a completedBlock is pointless 6 NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); 7 8 // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't 9 // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. 10 if ([url isKindOfClass:NSString.class]) { 11 url = [NSURL URLWithString:(NSString *)url]; 12 } 13 14 // Prevents app crashing on argument type error like sending NSNull instead of NSURL 15 if (![url isKindOfClass:NSURL.class]) { 16 url = nil; 17 } 18 19 __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; 20 __weak SDWebImageCombinedOperation *weakOperation = operation; 21 22 BOOL isFailedUrl = NO; 23 if (url) { 24 @synchronized (self.failedURLs) { 25 isFailedUrl = [self.failedURLs containsObject:url]; 26 } 27 } 28 29 if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { 30 [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url]; 31 return operation; 32 } 33 34 @synchronized (self.runningOperations) { 35 [self.runningOperations addObject:operation]; 36 } 37 NSString *key = [self cacheKeyForURL:url]; 38 39 operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { 40 if (operation.isCancelled) { 41 [self safelyRemoveOperationFromRunning:operation]; 42 return; 43 } 44 45 if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { 46 if (cachedImage && options & SDWebImageRefreshCached) { 47 // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image 48 // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. 49 [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 50 } 51 52 // download if no image or requested to refresh anyway, and download allowed by delegate 53 SDWebImageDownloaderOptions downloaderOptions = 0; 54 if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; 55 if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; 56 if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; 57 if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; 58 if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; 59 if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; 60 if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; 61 if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; 62 63 if (cachedImage && options & SDWebImageRefreshCached) { 64 // force progressive off if image already cached but forced refreshing 65 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; 66 // ignore image read from NSURLCache if image if cached but force refreshing 67 downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; 68 } 69 70 SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { 71 __strong __typeof(weakOperation) strongOperation = weakOperation; 72 if (!strongOperation || strongOperation.isCancelled) { 73 // Do nothing if the operation was cancelled 74 // See #699 for more details 75 // 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 76 } else if (error) { 77 [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url]; 78 79 if ( error.code != NSURLErrorNotConnectedToInternet 80 && error.code != NSURLErrorCancelled 81 && error.code != NSURLErrorTimedOut 82 && error.code != NSURLErrorInternationalRoamingOff 83 && error.code != NSURLErrorDataNotAllowed 84 && error.code != NSURLErrorCannotFindHost 85 && error.code != NSURLErrorCannotConnectToHost) { 86 @synchronized (self.failedURLs) { 87 [self.failedURLs addObject:url]; 88 } 89 } 90 } 91 else { 92 if ((options & SDWebImageRetryFailed)) { 93 @synchronized (self.failedURLs) { 94 [self.failedURLs removeObject:url]; 95 } 96 } 97 98 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); 99 100 if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { 101 // Image refresh hit the NSURLCache cache, do not call the completion block 102 } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { 103 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 104 UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; 105 106 if (transformedImage && finished) { 107 BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; 108 // pass nil if the image was transformed, so we can recalculate the data from the image 109 [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil]; 110 } 111 112 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; 113 }); 114 } else { 115 if (downloadedImage && finished) { 116 [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil]; 117 } 118 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; 119 } 120 } 121 122 if (finished) { 123 [self safelyRemoveOperationFromRunning:strongOperation]; 124 } 125 }]; 126 __weak typeof(subOperationToken)weakSubOperationToken = subOperationToken; 127 operation.cancelBlock = ^{ 128 [self.imageDownloader cancel:weakSubOperationToken]; 129 __strong __typeof(weakOperation) strongOperation = weakOperation; 130 [self safelyRemoveOperationFromRunning:strongOperation]; 131 }; 132 } else if (cachedImage) { 133 __strong __typeof(weakOperation) strongOperation = weakOperation; 134 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 135 [self safelyRemoveOperationFromRunning:operation]; 136 } else { 137 // Image not in cache and download disallowed by delegate 138 __strong __typeof(weakOperation) strongOperation = weakOperation; 139 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; 140 [self safelyRemoveOperationFromRunning:operation]; 141 } 142 }]; 143 144 return operation; 145 }
调用这个方法的时候没有 completedBlock 是毫无意义的。
如果想预先下载图片,使用 [SDWebImagePrefetcher prefetchURLs] 取代本方法,预下载图片是有很多使用场景的,当我们使用 SDWebImagePrefetcher 下载图片后,之后使用该图片时就不用再网络下载了。
Xcode 有时候经常会犯一些错误,当用户给 url 赋值时使用了字符串,但是 Xcode 并没有报错,这里的再次判断转化 NSURL 就是这种错误的处理。
防止应用程序崩溃在参数类型错误如发送 NSNull 代替 NSURL。
operation 会被作为该方法的返回值,但是 operation 的类型是 SDWebImageCombinedOperation,一个继承自 NSObject 的封装类,并不是一个 NSOperation。
在图片下载的时候,会有一些下载失败的情况,这个时候 SDWebImage 会把下载失败的 URL 放到一个收集无效的图片 URL 的黑名单集合里面。默认是不会再重新下载黑名单里面的 URL 了,但是也有例外,当下载选项设置为 SDWebImageRetryFailed 的时候,会把该 URL 从黑名单移除,会尝试进行重新下载。
会有两种情况让我们停止下载这个 URL 指定的图片。
1.url 的长度是 0。
2.下载选项没有设置为 SDWebImageRetryFailed 并且这个 url 在下载失败 URL 的黑名单中。
1 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation 2 completion:(nullable SDInternalCompletionBlock)completionBlock 3 error:(nullable NSError *)error 4 url:(nullable NSURL *)url { 5 [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url]; 6 } 7 8 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation 9 completion:(nullable SDInternalCompletionBlock)completionBlock 10 image:(nullable UIImage *)image 11 data:(nullable NSData *)data 12 error:(nullable NSError *)error 13 cacheType:(SDImageCacheType)cacheType 14 finished:(BOOL)finished 15 url:(nullable NSURL *)url { 16 dispatch_main_async_safe(^{ 17 if (operation && !operation.isCancelled && completionBlock) { 18 completionBlock(image, data, error, cacheType, finished, url); 19 } 20 }); 21 }
这时候直接调用完成的 block。
返回 operation。
排除了所有的错误可能后,我们就先把这个 operation 添加到正在运行操作的数组中。
这里没有判断 self.runningOperations 是不是包含了 operation。
说明肯定会在下边的代码中判断,如果存在就删除 operation。
self.imageCache 的 queryCacheOperationForKey: 方法是异步获取指定 key 的图片,
但是这个方法的 operation 是同步返回的,也就是说下边的代码会直接执行到 return 那里。
done 这个 block 会在查询完指定的 key 的图片后调用,由 "dispatch_async(self.ioQueue, ^{ });" 可以看出,是在异步线程采用串行的方式在调用,任务在 self.imageCache 的 ioQueue 队列中一个一个执行,是线程安全的。
如果每次调用 loadImage 方法都会生成一个 operation,如果我们想取消某个下载任务,在设计上来说,只要把响应的 operation.isCacelled 设置为 NO,那么下载就会被取消。并且从 self.runningOperations 中安全的移除。
1 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation 2 completion:(nullable SDInternalCompletionBlock)completionBlock 3 error:(nullable NSError *)error 4 url:(nullable NSURL *)url { 5 [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url]; 6 } 7 8 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation 9 completion:(nullable SDInternalCompletionBlock)completionBlock 10 image:(nullable UIImage *)image 11 data:(nullable NSData *)data 12 error:(nullable NSError *)error 13 cacheType:(SDImageCacheType)cacheType 14 finished:(BOOL)finished 15 url:(nullable NSURL *)url { 16 dispatch_main_async_safe(^{ 17 if (operation && !operation.isCancelled && completionBlock) { 18 completionBlock(image, data, error, cacheType, finished, url); 19 } 20 }); 21 }
再往下看:
根据是否有缓存的图片来做相应的处理。
如果没有获取到缓存的图片或者下载选项是 SDWebImageRefreshCached 需要刷新缓存图片,那么需要通过网络下载图片,这里多了一个额外的控制,根据 SDWebImageManagerDelegate 的代理方法:
1 - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
获取是否下载的权限。
这里需要注意了,当图片已经在缓存中了,Options 又选择了 SDWebImageRefreshCached 就会触发一次 completionBlock 回调,这说明这个下载的回调不是只触发一次的。如果使用了 dispatch_group_enter 和 dispatch_group_leave 就一定要注意一下了。
下面开始下载图片:
这里是 SDWebImageOptions 到 SDWebImageDownloaderOptions 的转换。" |= "。
图片已经在缓存中,并且下载选项是 SDWebImageRefreshCached 的比较特殊。
1.强制关闭进度下载,即使图片已经存在与缓存中,也强制刷新。 ~SDWebImageDownloaderProgressiveDownload 表示 ~0000 0010 => 1111 1101 (~ 表示求反码)
然后 0000 0100 & 1111 1101 => 0000 0100
2.忽略从 NSURLCache 里面读取图像,也强制刷新。
下载图片:
如果 strongOperation 不存在或者 strongOperation.isCancelled 是 YES,
则什么也不做。
发生错误就携带错误信息执行完成的 block。
除了 if 条件里面列举的错误情况外,把 url 加入 self.failedURLs 这个黑名单。
其它的应该就剩下下载成功的情况了:
首先:
如果下载选项是 SDWebImageRetryFailed 则先对 self.failedURLs 做移除 url 的操作,不管 self.failedURLs 中是否有 url。
cancheOnDisk 纪录是否把图片存入磁盘缓存中。
如果 options 是 SDWebImageRefreshCached 并且缓存图片 cacheImage 存在,downloadedImage 不存在。则什么都不做...
如果 downloadedImage 存在且 downloadedImage 不是动图,或者 options 是 SDWebImageTransformAnimatedImage 并且实现了
1 - (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
代理方法:
异步调取子线程,且使用 DISPATCH_QUEUE_PRIORITY_HIGH,
在子线程里面转化图像,修改图片完全由代理进行操作。
把图片存入缓存中。
执行完成的 block。
不需要转化图像的时候:
把图片存入缓存中。
执行完成的 block。
如果下载完成,把 strongOperation 从 self.runningOperations 删除。
设置取消的回调。
如果缓存图片存在,执行完成的 block,从 self.runningOperations 移除 operation。
如果缓存不存在,且下载被委托,也执行完成的 block,从 self.runningOperations 移除 operation。
返回 operation。
保存图片到内存缓存区
1 - (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url { 2 if (image && url) { 3 NSString *key = [self cacheKeyForURL:url]; 4 [self.imageCache storeImage:image forKey:key toDisk:YES completion:nil]; 5 } 6 }
取消所有的下载
1 - (void)cancelAll { 2 @synchronized (self.runningOperations) { 3 NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy]; 4 [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; 5 [self.runningOperations removeObjectsInArray:copiedOperations]; 6 } 7 }
查看是否下载完毕
1 - (BOOL)isRunning { 2 BOOL isRunning = NO; 3 @synchronized (self.runningOperations) { 4 isRunning = (self.runningOperations.count > 0); 5 } 6 return isRunning; 7 }
安全的移除任务
1 - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation { 2 @synchronized (self.runningOperations) { 3 if (operation) { 4 [self.runningOperations removeObject:operation]; 5 } 6 } 7 }
回调
1 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation 2 completion:(nullable SDInternalCompletionBlock)completionBlock 3 error:(nullable NSError *)error 4 url:(nullable NSURL *)url { 5 [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url]; 6 } 7 8 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation 9 completion:(nullable SDInternalCompletionBlock)completionBlock 10 image:(nullable UIImage *)image 11 data:(nullable NSData *)data 12 error:(nullable NSError *)error 13 cacheType:(SDImageCacheType)cacheType 14 finished:(BOOL)finished 15 url:(nullable NSURL *)url { 16 dispatch_main_async_safe(^{ 17 if (operation && !operation.isCancelled && completionBlock) { 18 completionBlock(image, data, error, cacheType, finished, url); 19 } 20 }); 21 }
SDWebImageCombinedOperation 实现
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 } 30 31 @end
当我们使用 SDWebImage 时,如果 Options 设置为 SDWebImageRefreshCached,那么这个 completionBlock 至少会调用两次,首先返回缓存中的图片,其次在下载完成后再次调用 Block。
参考链接:http://www.jianshu.com/p/a2cc208ee016
END