前言
关于sd_webImage是这样介绍的。Asynchronous image downloader with cache support with an UIImageView category. (一个异步下载且支持缓存的uiimageview分类)。
很多时候图片资源都是作为一个远程资源。为了加载图片需要根据url(统一资源定位符)去获取资源。我以前曾亲身体会到使用
正文
1 | [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];///1 |
两者带来极大不同效果。方法1是在主线程中货源图片资源并且加载,如果把这个事情放到列表中去做,滑动列表时会出现明显的卡顿效果,这是因为主线程因为加载资源被阻塞了。
使用方法2就是把资源获取通过异步的方式放在子线程中处理,取得之后通过回调的方式在加载图片。列表滑动会变得相当流程。
在对SD_webImage常用方法进行探索前,可以先明确一下实现这样一个异步加载器需要些什么。首先是”异步“,可以猜测其中关于根据图片url获取图片的操作不是在主线程进行的。然后是“缓存”,一个常见的缓存包含了内存缓存和磁盘缓存。所以在第一次获取到资源后应该会有一个类似cache保存的操作,减少下次重新下载耗费的时间。也许还会有一个磁盘缓存,app下次启动时如果发现已经存在这个资源也需要再次下载了。要做到这些需要一些缓存策略之类的东西。然后就是”分类”,这是一个对于uiimageView的分类,提供了一些新的分类方法。所以整个流程大概如下,uiimageview在加载图片时首先判断这个图片是否在内存中有无,有的话直接返回,没有的话再去本地路径中查找,有的话就返回,没有再去发起get请求,获取图片资源数据。获取之后再把数据存到内存中,同时存到本地。然后把数据通过回调返回。下面根据一次调用过程去看整个链路是如何工作的。
1 | - (void)sd_setImageWithURL:(nullable NSURL *)url |
这是uiview分类中的方法,因为作者也为buButton和NSButton提供了异步加载图片的能力,所以在他们共同的父类中扩展方法可以使其都可以调用。
在这个方法里面首先是一些上下文的操作,以UIImageview为例,首先尝试获取operationkey,对这个operationkey的解释是(pass through the operation key to downstream, which can used for tracing operation or image view class),意思就是方便追踪正在操作的视图类。获取到之后会
1 | self.sd_latestOperationKey = validOperationKey; |
意思就是如果validOperationKey对应的视图对象正在下载图片,会取消正在下载的操作。感觉这部分理解起来是要表达这么一个意思,因为对于不同的UIImageview对象会生成相同的operationkey,所以每次只会对一个uiimageview对象进行下载操作。(刚才想了下,我的想法有问题,虽然不通的UIImageview对象生成了相同的key,但是不同的对象应该持有的是不同的操作下载队列,不同的uiimageview对象是否同时在下载应该是NSURLSession关心的事情接下来就是把站位图放到视图上。接下来到调用下载方法之间还有一堆操作,大致理解一下就是在下载的过程中,增加一个指示器,根据下载进度更新指示器,完成之后移除指示器。
最主要的工作就是下面这个
1 | - (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url |
这个方法由SDWebImageManager进行管理,completedBlock中有很多的参数,特别关注下其中的cacheType,查看枚举定义,很明显的看出后面很根据cachetype的值决定是否下载。下载完成的会调用也是处理一些是否下载成功,是否出现错误的保护操作。继续进到下载的方法中查看,首先是对url的一些校验,最关键的还是其中的这个方法:
1 | // Start the entry to load image from cache |
这就是前面说的查询缓存的方法,根据shouldQueryCache 决定是否查询缓存,假定命中了需要查询缓存,然后就是根据url和context查询key值,在根据key值去查询。
///查询缓存
1 | - (id<SDWebImageOperation>)queryImageForKey:(NSString *)key |
到这里,发现东西越来越杂,看的有点头疼了。但是主要过程大致如下:
如果caches为空,直接返回,如果cache中一个值,直接查询,否则根据策略决定使用何种方式查询。选择默认的 SDImageCachesManagerOperationPolicySerial策略看下内部,内部主要调用的方法是:
1 | - (nullable id<SDWebImageOperation>)queryImageForKey:(nullable NSString *)key |
在最终内存查询的方法中,就是找到回调中带有image数据,没找到,回调中数据为nil,在末尾终于被我找到了,shouldQueryMemoryOnly,如果要执行disk查找的话, shouldQueryMemoryOnly返回no。最终都会返回一个doneBlock给外部。好,现在回到外层,如果一开始就认为不用查询缓存,就会执行下载操作:
1 | // Download process |
这一步就会去调用接口请求数据了,这个方法里面还有一个for循环,所以应该还存在并行调用的场景,最终的下载方法是:
1 | - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url |
通过这个方法会去下载图片,如果下载成功,就会调用存储方法:存储之后在讲对应的回调返回出去。
以上就是一次图片下载经过的整个过程,大致流程和一开始预测的过程类似。但是其中包含了设计者相当多的设计思路,尽量保证每一种场景case都考虑在内,所以其中if,esle场景判断相当多。
其次是其中代码的管理也相当规范,可以看到暴露给用户的就是一个简单的sd_setImageWithURL方法,但是内部完成的动作可以说是相当复杂。而且其中还有很多我当前没理解到的地方,比如说context的管理, 各个manager对于多种operation的管理。还有内部代码多是使用block回调处理的,很容易一下就看不明白了。
只能说这次缕了一下简单的过程,其中代码管理,各个模块协调的部分还需要继续往下看才行。