在 iOS 的图片加载框架中,SDWebImage 可谓是占据大半壁江山。它支持从网络中下载且缓存图片,并设置图片到对应的 UIImageView 控件或者 UIButton 控件。在项目中使用 SDWebImage 来管理图片加载相关操作可以极大地提高开发效率,让我们更加专注于业务逻辑实现。
SDWebImage 概论
1.提供了一个 UIImageView 的 category 用来加载网络图片并且对网络图片的缓存进行管理
2.采用异步方式来下载网络图片
3.采用异步方式,使用 memory+disk 来缓存网络图片,自动管理缓存。
4.支持 GIF 动画
5.支持 WebP 格式
6.同一个 URL 的网络图片不会被重复下载
7.失效的 URL 不会被无限重试
8.耗时操作都在子线程,确保不会阻塞主线程
9.使用 GCD 和 ARC
10.支持 Arm64
SDWebImage 使用
1.使用 ImageView+WebCache category 来加载 UITableView 中 cell 的图片
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
2.使用 block,采用这个方案可以在网络图片加载过程中得知图片的下载进度和图片加载成功与否
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//... completion code here ...
}];
3.使用 SDWebImageManager,SDWebImageManager 为UIImageView+WebCache category 的实现提供接口。
SDWebImageManager *manager = [SDWebImageManager sharedManager] ;
[manager downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
4.加载图片还有使用 SDWebImageDownloader 和 SDImageCache 方式,但那个并不是我们经常用到的。基本上面所讲的3个方法都能满足需求。
SDWebImage 流程
UIImageView的图片加载流程SDWebImage 接口
SDWebImage 是一个成熟而且比较庞大的框架,但是在使用过程中并不需要太多的接口,这算是一种代码封装程度的体现。这里就介绍比较常用的几个接口。
-
给 UIImageView 设置图片的接口,SDWebImage 有提供多个给UIImageView 设置图片的接口,最终所有的接口都会调用下图的这个接口,这是大多数框架的做法。
给UIImageView设置图片的接口 -
获取 SDWebImage 的磁盘缓存大小,在项目中有时候会需要统计应用的磁盘缓存内容大小,那么获取图片的缓存大小就是使用这个接口来实现
[SDImageCache sharedImageCache] getSize];
- 清理内存缓存,清理内存中缓存的图片资源,释放内存资源。
[[SDImageCache sharedImageCache] clearMemory];
- 有了清理内存缓存,自然也有清理磁盘缓存的接口
[[SDImageCache sharedImageCache] clearDisk];
SDWebImage 解析
解析主要围绕着 SDWebImage 的图片加载流程来分析,介绍SDWebImage 这个框架加载图片过程中的一些处理方法和设计思路。
-
给 UIImageView 设置图片,然后 SDWebImage 调用这个最终的图片加载方法。
1 给UIImageView设置图片 -
开始加载之前图片先取消对应的 UIImageView 先前的图片下载操作。试想,如果我们给 UIImageView 设置了一张新的图片,那么我们还会在意该 UIImageVIew 先前是要加载哪一张图片么?应该是不在意的吧!那是不是应该尝试把该 UIImageView 先前的加载图片相关操作给取消掉呢?
[self sd_cancelCurrentImageLoad]
2 取消对应的UIImageView先前的图片下载操作
该方法经过周转,最后调用了以下方法,框架将图片对应的下载操作放到 UIView 的一个自定义字典属性 (operationDictionary) 中,取消下载操作第一步也是从这个 UIView 的自定义字典属性 (operationDictionary)中取出所有的下载操作,然后依次调用取消方法,最后将取消的操作从(operationDictionary) 字典属性中移除。
最终的取消下载方法
3.移除之前没用的图片下载操作之后就创建一个新的图片下载操作,然后设置到 UIView 的一个自定义字典属性 (operationDictionary) 中。
3 创建一个新的图片下载操作
4.看看如何创建一个新的图片下载操作,框架保存了一个失效的 url 列表,如果 url 失效了就会被加入这个列表,保证不会重复多次请求失效的 url。
4 图片下载操作根据给定的 url 生成一个唯一的 key ,之后利用这个 key 到缓存中查找对应的图片缓存。
查找图片缓存
5.读取图片缓存,根据 key 先从内存中读取图片缓存,若没有命中内存缓存则读取磁盘缓存,如果磁盘缓存命中,那么将磁盘缓存读到内存中成为内存缓存。如果都没有命中缓存的话,那么就在执行的 doneBlock中开始下载图片。
5 读取图片缓存6.图片下载操作完成后会将图片对应的数据通过 completedBlock 进行回调
6 图片下载操作
在图片下载方法中,调用了一个方法用于添加创建和下载过程中的各类block 回调。
图片下载方法
添加该 url 加载过程的状态回调 block
状态回调Block
如果该 url 是第一次加载的话,那么就会执行 createCallback 这个回调block ,然后在 createCallback 里面开始构建网络请求,在下载过程中执行各类进度 block 回调。
构建网络请求
7.当图片下载完成之后会回到 done 的 block 回调中做图片转换处理和缓存操作
7 图片转换处理和缓存操作
回到 UIImageView 控件的设置图片方法 block 回调中,给对应的UIImageView 设置图片,操作流程到此完成。
Block中设置图片
8.304 的处理
SDWebImage在加载图片网络请求的 NSURLConnection 的代理中对httpCode 做了判断,当 httpCode 为 304 的时候放弃下载,读取缓存。
304处理
总结
SDWebImage 作为一个优秀的图片加载框架,提供的使用方法和接口对开发者来说非常友好。其内部实现多是采用 block 的方式来实现回调,代码阅读起来可能没有那么直观。此文章旨在给大家讲解 SDWebImage 这个框架的图片大概加载流程,其中具体细节限于篇幅无法详细深究。能力有限,文章中难免有错误,若大家在阅读过程中有发现不合理或者错误的地方恳请在评论中指出,我会在第一时间进行修正,不胜感激。
网友评论
// Apple's defines from TargetConditionals.h are a bit weird.
// Seems like TARGET_OS_MAC is always defined (on all platforms).
// To determine if we are running on OSX, we can only rely on TARGET_OS_IPHONE=0 and all the other platforms
#if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_WATCH
#define SD_MAC 1
#else
#define SD_MAC 0
#endif
上面的这段宏定义换成
#if !TARGET_OS_IPHONE
#define SD_MAC 1
#else
#define SD_MAC 0
#endif
这样应该也是的吗?
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock
这个方法在最后有个:
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
将operation放进字典之后,是从哪里执行这些operation方法的?
后面这个怎么理解呢?代理不能响应或代理调用了方法,俩个不是相反的吗,是不是可以不写的?
面试人B:我期望薪资是5k
面试官:小A你的情况我已经了解了,我想上门汇报一下,明天给你答复。
小B啊,你什么时候可以来上班呢?明天可以?最近项目比较赶!
// 如果内存缓存和磁盘缓存都没有找到,请求没被取消...,以下代码才会去尝试执行url对应的图片下载任务
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
// 图片下载完成
}
}