一 题外话
之前写过一篇最新版SDWebImage的使用,也简单的介绍了一下原理。这两天正梳理自己的知识网络,觉得有必要再阅读一下源码,一是看具体实现,二是学习一下优秀开源代码的代码风格,比如接口设计,设计模式,变量命名等等。
既然是第一篇,就要制定一个阅读源码的计划,以什么顺序阅读完全部代码。我们从最常见的入口切入sd_setImageWithURL,一路下去,最后再阅读没有设计到的部分。
在开始之前强烈建议先去读我之前的文章:最新版SDWebImage的使用。心里有个大概再去探讨细节,效果更佳。
二 入口
我们为什么使用SDWebImage,是因为他帮我们实现了图片的二级缓存,使我们加载图片更流畅。当然你也可以使用SDWebImage中几个很棒的工具类,比如SDWebImageDownloader,用来下载图片。或者SDImageCache用来缓存图片或者NSData。我们先来看看UIImageView+WebCache中的基本方法:
在UIImageView+WebCache类的最上面,很贴心的贴了一个使用例子,这也是我们很常见的tableViewCell加载图片的场景
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier]
autorelease];
}
// Here we use the provided sd_setImageWithURL: method to load the web image
// Ensure you use a placeholder image otherwise cells will be initialized with no image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://example.com/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder"]];
cell.textLabel.text = @"My Text";
return cell;
}
而sd_setImageWithURL: placeholderImage:,也是我们最常使用的方法,我们看看除了这个外的其他方法:
//最基本方法
- (void)sd_setImageWithURL:(NSURL *)url;
//带placeholder,优先显示placeholder,下载完成后显示原图
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
//多了缓存策略options
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
//多了下载完成后的block回调
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
//多了下载完成后的block回调
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
//多了下载完成后的block回调
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
//多了下载过程progressBlock回调
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
//多了下载过程progressBlock回调
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
这个接口设计很经典,也是看了这个接口设计,回头重构了自己项目中社交分享模块的接口。而下面这个方法,也可以看做是外观模式的具体体现。
- (void)sd_setImageWithURL:(NSURL *)url;
废话少说,进去看看实现。可以看到,所有方法都指向
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
具体实现如下
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelCurrentImageLoad];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
if (url) {
// check if activityView is enabled or not
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator];
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
dispatch_main_async_safe(^{
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
可以看到第一步是取消当前下载,本着不放过任何一个细节的精神,我们看看是怎么取消当前下载的
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
// Cancel in progress downloader from queue
NSMutableDictionary *operationDictionary = [self operationDictionary];
id operations = [operationDictionary objectForKey:key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
我们看到,operationDictionary就是多个线程的集合。在SDWebImageManager的downloadImageWithURL方法中创建operation并返回,保存在operationDictionary中。然后我们从operationDictionary中通过key "UIImageViewImageLoad" 取出负责下载的所有operation,然后cancel掉。并将下载的所有operation从operationDictionary中移除。特别值得注意的是,当前类是UIImageView的category,我们知道,category不能增加属性,只能增加方法,那么operationDictionary是哪里来的呢。答案是:objc_setAssociatedObject,对象关联,动态的给UIImageView添加新属性。在SDWebImage中有很多这种用法,看到你就要知道,这就是动态增加了属性。
继续看如何cancel,SDWebImageOperation是一个协议,而SDWebImageDownloaderOperation实现了这个协议。我们上面说的从operationDictionary中通过key "UIImageViewImageLoad" 取出负责下载的所有queue,其实就是SDWebImageDownloaderOperation的实例的集合。熟悉NSOperation的都知道,SDWebImageDownloaderOperation继承于NSOperation,每一个SDWebImageDownloaderOperation的实例都是一个新线程。NSOperation其实自己也抽象了cancel方法。- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key方法中,[operation cancel];这里的operation其实是SDWebImageManager的
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
这个方法中创建的SDWebImageCombinedOperation实例,
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
再看看SDWebImageCombinedOperation的定义和实现
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@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
我们看到,调用该operation的cancel方法,其实是执行cancelBlock,我们就看看,他的cancelBlock传入的是什么东西。在SDWebImageManager的downloadImageWithURL方法中,我们找到了赋值的地方
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
我们看到block里面调用了[subOperation cancel];而这个subOperation,是SDWebImageDownloader的downloadImageWithURL方法创建并返回的SDWebImageDownloaderOperation对象,它是NSOperation的子类。终于到了我们熟悉的对象。究其原因,cancle的时候其实就是SDWebImageDownloaderOperation的实例cancel,具体实现如下:
- (void)cancel {
@synchronized (self) {
if (self.thread) {
[self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
else {
[self cancelInternal];
}
}
}
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.cancelBlock) self.cancelBlock();
if (self.dataTask) {
[self.dataTask cancel];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
最重要的就是这句:[self.dataTask cancel]。self.dataTask是NSURLSessionTask的实例,这里就是取消网络请求。说这么多,仅仅是取消了下载图片的网络请求。
已经写了不少了,把大头戏放到下一篇。下一篇我们主要分析二级缓存的实现和SDWebImageDownloader的异步加载。