SDWebImage 源码分析
SDWebImage 源码分析
首先我 fork 了 SDWebImage 的源码,见 conintet/SDWebImage,这样在本文的链接中都是链到我的 fork 中,这么做的目的是防止将来 SDWebImage
代码发生变化导致本文的链接不准确。
有关 SD (SDWebImage 简称为 SD) 的使用方式还是得参考其 README 或者 wiki。本文只是阅读其源码的笔记。
图片下载
最先分析的就是图片下载部分的代码,因为这是最核心的功能。
因为 SD 在 UIImageView
上通过 Category
的方式增加了简单易用的 API,类似下面:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
于是通过几步 Jump to Definition 就可以发现,SD 的图片下载操作是由 SDWebImageDownloaderOperation
来完成的,于是看一下它的初始化方法:
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
通过上面的方法签名,可以大概反向的知道:
- 使用了
NSURLRequest
,那么很可能内部就使用的NSURLConnection
来完成的下载 - 既然提供了
progress
、completed
这两个 callback,那么内部势必需要知道下载的进度 - 因为提供了
cancelled
这个 callback,那么内部的下载操作还需要可以取消
再看一下 SDWebImageDownloaderOperation
是继承于 NSOperation
,因为下载是一个可以独立出来的计算单元,所以作为 Opreation 是很好理解的。然后在实际的图片下载中,为了下载的效率,下载的 Opreations 之间肯定是需要并发的。Operation 默认在其被调用的线程中是同步执行的,不过由于 Operation Queue 的存在,它可以将其中的 Operations 分别 attach 到由系统控制的线程中,而这些由系统控制的线程之间是并发执行的。
查看 SDWebImageDownloaderOperation
源码发现内部果然是使用的 NSURLConnection
,那么由于需要提供 cancelled
的功能以及需要监听下载进度,故必须将 NSURLConnection
的实例配置成异步的方式:
具体代码在 L96
// 配置异步 NSURLConnection 的方式
// 实例化一个 NSURLConnection,并将自身(SDWebImageDownloaderOperation)设置为 NSURLConnection 实例的委托
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
// 因为上一步的 startImmediately:NO,所以这里手动的触发 start
// 这样的效果和直接 startImmediately:YES 是一样的
[self.connection start];
// 因为上面两步结合起来或者直接 startImmediately:YES 的结果就是下载例程将会在当前 Run Loop 上以默认的模式进行调度,
// 而在 iOS 中除了主线程之外的线程都是默认没有运行 Run Loop 的,所以需要手动的运行一下
CFRunLoopRun();
// 之后的代码将会被 CFRunLoopRun() 所阻塞,这样 operation 所在的线程
// 就不会自动的退出,于是需要额外的代码在下载完成之后手动的停止 RunLoop 使得
// operation 所在的线程可以退出
对于下载进度的监听,SDWebImageDownloaderOperation
是通过将自身设置为 NSURLConnection
委托的形式完成的:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 在这一委托方法的实现中,SDWebImageDownloaderOperation
主要是获取服务端响应的 meta 信息,尝试根据响应的 statusCode 对下载过程进行预判,比如如果是 304 状态码直接从本地缓存中返回图片。但是这里的代码写的有些繁琐了,并且性能上也是存在些问题。首先可以看下这幅概览图:
为什么不能赋值?因为指针的捕获也是作为了 const
,和基本类型一样。
总结起来说就是,objc 对象在 block 中捕获的是指向其真实地址的指针,指针以 const
的形式被捕获,不使用 __block
修饰就无法改变指针的内容,但是对于指针指向的对象,它们的内容还是可以改变的。
[2015-11-26 修正]
上面的关于 NSNotification 的说明有些纰漏,修正见 NSNotificationCenter