最近几个项目在使用到图片的时候都采取异步加载的情况,然而原生的ImageView的相关方式是同步执行的,如果图片太大或者网络条件不佳,那么就会造成主界面卡死。所以我们最后才用的是SDWebImage这个库。像往常一样,我们还是来简单分析一下SD都做了哪些事情。
SD的源码大致可分为两部分,功能类和Category,功能类包含如下文件,我们来一个个地分析。
SDImageCache负责管理图片的缓存,包括使用NSCache实现内存缓存以及使用NSFileManger实现磁盘缓存,对外提供了配置参数、存储/删除/查询图片缓存的方法。
SDWebImageCompat负责对Image进行缩放处理,以兼容2x/3x。这里有一个细节需要注意:
inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image)
方式用了inline进行了修饰,inline的好处是对于需要频繁调用的方法,可以提高运行效率。具体可参见这里。于是我按照博客上的例子试了试,看了一下汇编的代码,发现并没有call与不call的区别,都是"bl _add",于是又Google了一下,发现了这个
这里面提到即使使用inline,编译器也不一定就这么做,最终决定权在编译器,代码里的声明只是建议。而且现在CPU速度很快,递归栈的速度也基本忽略不计了,对效率的影响并不大。
SDWebImageDecoder负责图片的解码。这里有个点,即通过 imageNamed 创建 UIImage 时,系统实际上只是把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用。这样带来的结果就是对于特别大的图,在显示的时候会出现延迟。
SDWebImageDownloader负责图片的下载。包括设置是否解码、并发量、使用NSOperationQueue管理请求等。
SDWebImageDownloaderOperation继承NSOperation,处理业务层传递来的回调事件。
SDWebImageManager负责对业务层提供下载图片、缓存图片、检查图片是否存在等方法。
SDWebImagePrefetcher可以预下载图片,方便以后使用,个人理解就是比直接用Manager预加载更方便一点,因为作者已经把相关的代码封装好了。
下面以最常用的[UIImageView sd_setImageWithURL]方法为例,分析一下SD的大致流程:
1)取消之前的请求;
[self sd_cancelCurrentImageLoad];
2)设置占位图;
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
3)通过SDWebImageManager处理下载逻辑,并将此次加载图片的请求保存,以便cancel;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//......
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
4)检查image是否在内存中存在;
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
5)检查image在磁盘中是否存在;
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
6)通过imageDownloader下载图片;
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
7)在内存和磁盘中保存image;
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
8)处理业务层的回调;
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);