SDWebImage 如何为 UIImageView 添加图片(面试回答)
SDWebImage 中为 UIView 提供了一个分类叫做 WebCache, 这个分类中有一个最常用的接口, sd_setImageWithURL:placeholderImage:, 这个分类同时提供了很多类似的方法, 这些方法最终会调用一个同时具有 option progressBlock completionBlock 的方法, 而在这个类最终被调用的方法首先会检查是否传入了 placeholderImage 以及对应的参数, 并设置 placeholderImage.
然后会获取 SDWebImageManager 中的单例调用一个 downloadImageWithURL:... 的方法来获取图片, 而这个 manager 获取图片的过程有大体上分为两部分, 它首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存, 如果缓存未命中就会在磁盘中利用 MD5 处理过的 key 来继续查询对应的数据, 如果找到了, 就会把磁盘中的缓存备份到内存中.
然而, 假设我们在内存和磁盘缓存中都没有命中, 那么 manager 就会调用它持有的一个 SDWebImageDownloader 对象的方法 downloadImageWithURL:... 来下载图片, 这个方法会在执行的过程中调用另一个方法 addProgressCallback:andCompletedBlock:fotURL:createCallback: 来存储下载过程中和下载完成的回调, 当回调块是第一次添加的时候, 方法会实例化一个 NSMutableURLRequest 和 SDWebImageDownloaderOperation, 并将后者加入 downloader 持有的下载队列开始图片的异步下载.
而在图片下载完成之后, 就会在主线程设置 image 属性, 完成整个图像的异步下载和配置.
SDWebImageDownloaderOperation中的 runloop
在SDWebImage中的SDWebImageDownloaderOperation的start函数中,调用了CFRunLoopRun(),我们来看一下CFRunLoopRun到底是做什么的,起到了什么作用。
每一个线程有一个runloop,既不可以创建,也不能销毁线程的runloop。Core Foundation根据需求为你创建,通过CFRunLoopGetMain
可以获得当前线程的runloop。调用CFRunLoopRun可以使当前线程的runloop以默认模式运行起来,直到调用CFRunLoopStop
来停止runloop。你也可以调用CFRunLoopRunInMode
来使当前线程的runloop以指定模式运行起来一段时间或者直到runloop被停止。【runloop只能在请求模式至少有一个source或者timer可监控的情况下运行起来。】
一般主线程会自动运行runloop,我们一般情况下不会去管。在其他子线程中,如果需要我们需要去管理。使用runloop后,可以把线程想象成进入了一个循环;如果没有这个循环,子线程完成任务后,这个线程就结束了。所以如果需要一个线程处理各种事件而不让它结束,就需要运行runloop。
在SDWebImageDownloaderOperation中,
- (void)start{
...
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
...
if(self.connection){
...
CFRunLoopRun()
...
}
}
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
CFRunLoopStop(CFRunLoopGetCurrent());
}
在创建self.connection成功后,执行了CFRunLoopRun(),开启了runloop。在failed或finished的时候会调用CFRunLoopStop停止runloop。如果不开启runloop的话,在执行完start()后任务就完成了,NSURLConnection的代理就不会执行了。runloop相当于子线程的循环,可以灵活控制子线程的生命周期。