上一篇中我们介绍了常用的UIImageView使用SDWebimage加载图片的方法,遗留了问题,一个是在核心代码开始时要取消当前的operation,一个是下载图片返回operation的方法,下面我们重点解析这个图片的的下载方法。
我们点开源码发现,该方法在SDWebImageManager中,在常用的第三方框架中都会用到这个方法来管理一些事务。我们先看SDWebImageManager的.h文件。
文件开始时一系列枚举:
SDWebImageRetryFailed:图片下载失败重新下载
SDWebImageLowPriority:下载的优先级
SDWebImageCacheMemoryOnly:只进行内存缓存
SDWebImageProgressiveDownload:显示图片的下载进度
SDWebImageRefreshCached:刷新缓存
SDWebImageContinueInBackground:后台继续下载图片
SDWebImageHandleCookies:cookies处理方式
SDWebImageAllowInvalidSSLCertificates:接受无效的SSL证实
SDWebImageHighPriority:高优先级
SDWebImageDelayPlaceholder:延迟加载placeholder
SDWebImageTransformAnimatedImage:改变动画图片
SDWebImageAvoidAutoSetImage: //当图片下载完以后要对下载后的图片进行处理,处理完成后再使用图片
代理:SDWebImageManagerDelegate
方法:
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
对象方法:
核心方法
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
其他方法:
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
- (void)cancelAll;
- (BOOL)isRunning;
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
- (BOOL)diskImageExistsForURL:(NSURL *)url;
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (NSString *)cacheKeyForURL:(NSURL *)url;
在SDWebImageManager.m文件中,我们先来看这个核心的下载图片的方法,这个方法比较长,我们分为几部分来看
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
//整个block必须实现
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 进行处理
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//创建一个SDWebImageCombinedOperation的operation
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//判断当前的url 是否包含在下载失败的url集合中,在访问failedURLs时要进行加锁,防止其他线程对其进行操作
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
//如果url在下载失败的url集合中并且不是下载失败重新下载(url的长度为0)那么抛出错误
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];
}
//从缓存中根据url取出key(其实key就是url的string)
NSString *key = [self cacheKeyForURL:url];
.
.
.
在第一部分中创建了一个SDWebImageCombinedOperation的operation遵守了SDWebImageOperation协议,这个需要解惑这里为什么要使用这个方法来创建operation
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
// 注意SDWebImageCombinedOperation遵循SDWebImageOperation,所以实现了cancel方法
// 在cancel方法中,主要是调用了cancelBlock,这个设计很值得琢磨
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
// 根据cacheType获取到image,这里虽然名字用的是cache,但是如果cache没有获取到图片
// 还是要把image下载下来的。此处只是把通过cache获取image和通过download获取image封装起来
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
第二部分:
这里第二部分涉及到从缓存中取图片的问题,因此调用了 SDImageCache类中的- (NSOperation )queryDiskCacheForKey:(NSString )key done:(SDWebImageQueryCompletedBlock)doneBlock 方法,我们随后在研究这个类中的东西,我看先看看如果manager中对这个方法中的block的处理,也就是结果的处理方法。
这里block中的代码又很长,我们仍然分块来看看
(1)先看这个判断,我们看后半部分(&&后面的)调用代理方法imageManager:shouldDownloadImageForURL:,你可以在源码中搜索这个方法,发现源码中没有实现这个方法,所以后面部分始终是YES,前面部分就是如果图片为nil或需要刷新内存,就执行下面的方法
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {...}
我先先看有image并且不需要刷新内存的情况
...
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
//将该operation(SD_operation:自定义的operation)从数组中移除
@synchronized (self.runningOperations) {
[self.runningOperations removeObject: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.runningOperations removeObject:operation];
}
}
在看上面的没有图片或需要刷新缓存
(1)有图片并且需要刷新缓存,那么就在主线程刷新
//有图片 需要刷新缓存信息,那么在主线程中调用completedBlock传出image和获取image的途径
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);
});
}
(2)如果图片为空根据传入的options的状态,与SDWebImageDownloader类中的枚举进行同步
// 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;
//SDWebImageDownloaderIgnoreCachedResponse:因为SDWebImage有两种缓存方式,一个是SDImageCache,一个就是NSURLCache,所以知道为什么这个选项是Ignore了吧,因为已经从SDImageCache获取了image,就忽略NSURLCache了。
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;
}
(3)下面又调用了SDWebImageDownloader的对象方法来下载图片,这里又涉及到了SDWebImageDownloader这个类,这个类我们也以后再说我们先看得到图片后block中的操作。
上面是对下载图片失败的错误情况的处理,如果不是与网络相关方面的问题说明下载的图片本身的url有问题,那么将它加入到failedURLs中。说明改url时不能下载的url
...
else {//如果当前url 为失败重试 将url从failedURLs移除
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否只能从内存中取出
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//如果需要刷新缓存 并有image 单没有最新下载的image 则做操作
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
}
//如果下载了image 并且需要对image进行处理,并且实现了图片处理的代理方法
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];
}
//主线程中同步调用completedBlock 将处理后的图片传出去
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];
}
//主线程中同步调用completedBlock 将处理后的图片传出去
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
}
该block中处理对图片的处理也用到SDImageCache类。我们在下一篇中会介绍这个类。
其他东东:
SDImageCache:缓存类(manager的一个属性)
SDWebImageDownloader:下载类(manager的一个属性)
NSMutableSet *failedURLs:不能下载的图片的url集合
@property (strong, nonatomic) NSMutableArray *runningOperations;
:下载中的operation数组
SDWebImageCombinedOperation类遵守了SDWebImageOperation协议,重写了block的set方法和协议的cancel方法
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
// 注意SDWebImageCombinedOperation遵循SDWebImageOperation,所以实现了cancel方法
// 在cancel方法中,主要是调用了cancelBlock,这个设计很值得琢磨
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
// 根据cacheType获取到image,这里虽然名字用的是cache,但是如果cache没有获取到图片
// 还是要把image下载下来的。此处只是把通过cache获取image和通过download获取image封装起来
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}
@end
总结:
这个类中主要是对图片下载和缓存方式的管理以及对下载好的图片、地址url等信息的block处理,承上启下的作用,承接了UIImageView+WebCache类,下接SDImageCache类和SDWebImageDownloader类。下篇中我们主要研究缓存的类(SDImageCache)。
遗留问题:
1、UIImageView+WebCache核心代码中开始停止所有的operation;
2、SDWebImageCombinedOperation类的原因好处以及出现的subOperation的原因
3、SDImageCache缓存类的实现方式
4、SDWebImageDownloader下载类的实现
我们会下面的几篇中逐步解决这些问题。