SDWebImage源码详解 - SDWebImage管理器SDWebImageManager
前面两节,我们介绍了SDWebImage的缓存SDImageCache和下载SDWebImageDownloader,这一节我们来介绍一下SDWebImageManager,这个类是主要用来管理缓存和下载对象。在平常的使用当中,我们不直接使用SDWebImageDownloader类及SDImageCache类来执行图片的下载及缓存。而是通过这个SDWebImageManager类来调用他们,实现一个管理器的作用,是一个单例类。UIImageView+WebCache这个category背后执行操作的就是这个SDWebImageManager.
首先我们来看一下SDWebImageManager.h头文件的实现。
//定义一些操作选项,里面有些操作选项是和SDWebImageDownloaderOptions中的选项对应的
//在-(id)downloadImageWithURL:options:progress:completed:函数中我们可以看到这些选项是如何和SDWebImageDownloaderOptions中的选项对应起来的
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
//默认情况下,当URL下载失败时,URL会被列入黑名单,下次便不会再去重试,该标记用于禁用黑名单
SDWebImageRetryFailed = 1 << 0,
// 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时
SDWebImageLowPriority = 1 << 1,
// 该标记禁用磁盘缓存
SDWebImageCacheMemoryOnly = 1 << 2,
// 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。
// 默认情况下,图像在下载完成后一次性显示
SDWebImageProgressiveDownload = 1 << 3,
// 即使图片缓存了,也期望HTTP响应cache control,并在需要的情况下从远程刷新图片。
// 磁盘缓存将被NSURLCache处理而不是SDWebImage,因为SDWebImage会导致轻微的性能下载。
// 该标记帮助处理在相同请求URL后面改变的图片。如果缓存图片被刷新,则完成block会使用缓存图片调用一次
// 然后再用最终图片调用一次
SDWebImageRefreshCached = 1 << 4,
// 在iOS 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成
// 如果后台任务超时,则操作被取消
SDWebImageContinueInBackground = 1 << 5,
// 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES
// 来处理存储在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
// 允许不受信任的SSL认证
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
// 默认情况下,图片下载按入队的顺序来执行。该标记将其移到队列的前面,
// 以便图片能立即下载而不是等到当前队列被加载
SDWebImageHighPriority = 1 << 8,
// 默认情况下,占位图片在加载图片的同时被加载。该标记延迟占位图片的加载直到图片已以被加载完成
SDWebImageDelayPlaceholder = 1 << 9,
// 通常我们不调用动画图片的transformDownloadedImage代理方法,因为大多数转换代码可以管理它。
// 使用这个票房则不任何情况下都进行转换。
SDWebImageTransformAnimatedImage = 1 << 10,
// 通常情况下,当图片下载完成后就会被加载置iamgeView显示。但是有些情况下,我们想在图片被显示之前做一些处理(应用一个过滤器或者添加一个动画)。
//当你想手动的在图片下载完成后处理图片,就设置这个标记
SDWebImageAvoidAutoSetImage = 1 << 11
};
//声明一些回调
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
//定义一个协议SDWebImageManagerDelegate
//这个协议主要实现一些在下载过程中对图片的操作
@protocol SDWebImageManagerDelegate <NSObject>
@optional
// 控制当图片在缓存中没有找到时,应该下载哪个图片,可以选择no,那么sdwebimage在缓存中没有找到这张图片的时候不会选择下载
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
// 允许在图片已经被下载完成且被缓存到磁盘或内存前立即转换。异步执行
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end
@interface SDWebImageManager : NSObject
//协议对象属性
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
//缓存对象
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//下载器对象
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
//这个cacheKeyFilter是一个block.这个block的作用就是生成一个image的key.
//因为sdwebimage的缓存原理你可以当成是一个字典,每一个字典的value就是一张image,
//cacheKeyFilter根据某个规则对这个图片的url做一些操作生成的
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
//单例函数
+ (SDWebImageManager *)sharedManager;
//这个就是SDWebImageManager类的核心所在
//第一个参数是必须要的,就是image的url
//第二个参数就是我们上面的Options,你可以定制化各种各样的操作.详情参上.
// 第三个参数是一个回调block,用于图片在下载过程中的回调.
// 第四个参数是一个下载完成的回调.会在图片下载完成后回调.
// 返回值是一个NSObject类,并且这个NSObject类是conforming一个协议这个协议叫做SDWebImageOperation,这个协议很简单,就是一个cancel掉operation的协议.
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//将图片存入cache的方法,键为url
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消掉当前所有的下载图片的操作operation
- (void)cancelAll;
//查看是否还有下载操作在执行
- (BOOL)isRunning;
//通过一个image的url检查缓存中是否已经存在这个图片,如果存在返回yes,否则返回no
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
//检测一个image是否已经被缓存到磁盘(是否存且仅存在disk里)
- (BOOL)diskImageExistsForURL:(NSURL *)url;
//如果检测到图片已经被缓存,那么执行回调block.
//这个block会永远执行在主线程.也就是你可以在这个回调block里更新ui
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//如果检测到图片已经被缓存在磁盘(存且仅存在disk),那么执行回调block.
//这个block会永远执行在主线程.也就是你可以在这个回调block里更新ui
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//通过image的url返回image存在缓存里的key.
- (NSString *)cacheKeyForURL:(NSURL *)url;
@end
可以看出SDWebImageManager主要是绑定一个imageCache和一个下载器,通过一些属性和对外的函数接口来实现对imageCache和下载器的操作。这个类的核心在于这个函数:-(id<SDWebImageOperation>)downloadImageWithURL:options:progress:completed:
,这个函数的具体实现:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// 如果没有完成回调函数,则提示If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//判断url的合法性
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//查看url是否是之前下载失败过的
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
//如果url为nil,或者在不可重试的情况下是一个下载失败过的url,则直接返回操作对象并调用完成回调
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;
}
//将这个操作加入到正在执行的operation数组中
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
//cacheOperation是一个用来下载图片并且缓存的operation
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
//判断operation这时候有没有执行cancel操作,
//如果cancel掉了就把这个operation从我们的正在执行的operation数组里remove掉然后return
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
//如果图片在缓存中不存在或者满足一些设置,则创建一个下载器去下载图片
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
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.
//如果在缓存中找到图片,但是SDWebImageRefreshCached置位,
//则尝试重新下载,让NSURLCache有机会得到刷新
completedBlock(image, nil, cacheType, YES, url);
});
}
//开始下载
//下面都是判断我们的options里包含哪些SDWebImageOptions,
//然后给我们的downloaderOptions相应的添加对应的SDWebImageDownloaderOptions
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;
}
// 调用imageDownloader去下载image并且返回执行这个request的download的operation
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// 操作被取消,则不做任务事情
}
// 如果出错,则调用完成回调,并将url放入下载失败url数组中
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.failedURLs addObject:url];
}
}
}
else {
//如果设置了SDWebImageRetryFailed则将下载失败的url从failedURLs数组中删除
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject: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.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
}
else { //下载结束,则将图片保存置缓存
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
//在主线程中调用完成回调函数
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
}
//执行结束,将这个operation从我们的正在执行的operation数组里remove掉
if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
//设置取消回调block
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
//如果在缓存中找到图片则直接执行完成回调,并把这个operation从我们的正在执行的operation数组里remove掉
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
//其他情况
else {
// Image not in cache and download disallowed by delegate
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}];
return operation;
}
到这里我们SDWebImage的源码讲解结束了,这里只是介绍了SDWebImage的主要实现,SDWebImage还实现了一些扩展,UIImageView、UIView、UIButton、MKAnnotationView等视图类,大家可以参考源码进行更深入的研究。
网友评论