一 题外话

  之前写过一篇最新版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:- 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的异步加载。

SDWebImage源码阅读-第一篇的更多相关文章

  1. Flask源码阅读-第一篇(flask包下的__main__.py)

    源码: # -*- coding: utf-8 -*-""" flask.__main__ ~~~~~~~~~~~~~~ Alias for flask.run for ...

  2. SDWebImage源码阅读-第二篇

    一  SDWebImageManager的downloadImageWithURL的方法 上一篇,我们刚开了个头,分析了一下开始加载图片之前如何取消其他正在下载的任务,接着,我们回到 - (void) ...

  3. SDWebImage源码阅读-第三篇

    这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: com ...

  4. 【原】SDWebImage源码阅读(二)

    [原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还 ...

  5. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  6. 【原】SDWebImage源码阅读(四)

    [原]SDWebImage源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 SDWebImage中主要实现了NSURLConnectionDataDelega ...

  7. 【原】SDWebImage源码阅读(三)

    [原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们 ...

  8. 【原】SDWebImage源码阅读(一)

    [原]SDWebImage源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 一直没有系统地读过整套源码,就感觉像一直看零碎的知识点,没有系统读过一本专业经典书 ...

  9. SDWebImage 源码阅读分享

    SDWebImage 源码阅读分享 疑问列表 SDWebImage 整体框架图,主要的类包含哪些 SDWebImage 如何进行缓存管理,过期失效策略,缓存更新 SDWebImage 如何多线程处理的 ...

随机推荐

  1. MidpointRounding 枚举值简要说明

    1. MidpointRounding.AwayFromZero 当小数点后取舍时5 时会取绝对值大的如 4.5 会取5 及正常的4舍5入. -- 官方解释翻译解释取绝对值小值感觉反译错了. 2.Mi ...

  2. 在Oracle中添加用户登录名称

    第一步,打开Oracle客户端单击 “帮助”-->"支持信息"-->”TNS名“,加入红色部分.页面如下: 第二步,再次打开Oracle客户端时,就会显示数据库了,只需 ...

  3. AngularJS创建新指令 - 函数功能

    首先先介绍下AngularJS指令下的几种函数 Link函数和Scope 指令生成出的模板其实没有太多意义,除非它在特定的scope下编译.默认情况下,指令并不会创建新的子scope.更多的,它使用父 ...

  4. bzoj4818 [Sdoi2017]序列计数

    Description Alice想要得到一个长度为n的序列,序列中的数都是不超过m的正整数,而且这n个数的和是p的倍数.Alice还希望,这n个数中,至少有一个数是质数.Alice想知道,有多少个序 ...

  5. 玩玩kafka1 单机安装

    今天主要来一遍kafka单机版的安装,比较简单易上手,关于kafka的介绍我就不贴了,大家可以自行搜索 1.首先将tgz包传到centos目录下(这里使用xftp工具) ok后查看一下tgz包是否已经 ...

  6. nginx 高可用

    1 nginx负载均衡高可用 1.1 什么是负载均衡高可用 nginx作为负载均衡器,所有请求都到了nginx,可见nginx处于非常重点的位置,如果nginx服务器宕机后端web服务将无法提供服务, ...

  7. 谈谈对Spring IOC的理解(转载)

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  8. 如何利用动态URL提升SEO及处理业务逻辑

    如果你正在建设一个新网站或者对现有网站重新设计,我们认为应该将网站的 URL 转换为用户友好的 URL,或搜索引擎友好的 URL,这类 URL 也称为语义 URL(Semantic URL).哪些UR ...

  9. Maven学习-优化和重构POM

    在一个复杂的项目中,项目的各个模块存在各种相互依赖关系.优化一个多模块项目的POM最好通过几步来做.总的来说,我们总是寻找一个POM中的重复或者多个兄弟POM中的重复.在多模块项目中依赖重复的模式主要 ...

  10. 【JAVAWEB学习笔记】14_response

    HttpServletResponse 学习目标 案例一.完成文件下载 案例二.生成验证码(了解) 1.HttpServletResponse概述 我们在创建Servlet时会覆盖service()方 ...