网络图片缓存刷新问题
为了提高客户端内容加载速度与效率,在加载网络图片时,会使用缓存,而SDWedImage可以说是iOS 网络图片加载的事实标准了。但它并没有一种较好的缓存策略,解决URL不变图片改变的情况。
问题
一般来说,网络图片改变,URL会跟着改变,所以绝大部分的使用场景里,使用SDWedImage的默认缓存策略是不会有问题的。但有时候后台换了图片就是不换名字,而且你会发现这些图片在浏览器(可能是wap端)是会跟着改变的。嗯,这时候如果管技术的不够6,后台就成功卸锅了。
解决思路
SDWebImage默认的缓存策略,是根据url这个key寻找本地是否存在缓存,存在就加载,不发任何请求。
当然,还是有可选的缓存策略里,比如SDWebImageRefreshCached这个选项,这个选项其实就是使用NSURLCache,然而这个自带的缓存策略最优选也不过是每次都下载图片回来刷新。
从网页端入手
从框架本身找不到思路,不妨看看浏览器是怎么实现这种资源刷新的,先抓包看看
第一次加载图片
第二次
资源改变
可以看到,加载过的图片再去加载就会返回304,而304就是代表服务器判定客户端存在资源缓存,而且缓存在有效期内,让客户端加载缓存即可。
服务器如何判定304
主要是根据请求头种的 if-non-match 和 if-modified-since,当这两个字段有任何一个不匹配都需要重新下载资源,而浏览器中的这两个请求头字段是根据上次请求这个url时,服务器返回的响应头中的etag和last-modified生成的。
我这次使用的是nginx,默认返回的last-modified字段是文件的创建时间,对时间的判断是“是否相等”,而不是“大于小于”。暂时没有网上所说的有些公司为了压榨服务器性能,把etag验证关了的情况。也就是说完全可以根据请求头里的这两个字段作文章了。
代码实现
由上分析可知,需要缓存每次图片请求的响应头的etag和last-modified,那么就在sdwebimage的响应方法里面作缓存,我使用aspect这个框架去hook这个方法,代码如下
1 | [SDWebImageDownloader aspect_hookSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:) |
接着每次进行网络图片请求时,加上对应的请求头,这个sdwedimage有提供对应的接口方法,初衷可能是给一些图片下载有验证的服务器用的,这里用也正好合适,封装成一个方法,在app启动时调用即可,代码如下
1 | - (void)setURLImage { |
至此,给sdwedimage实现了一种与浏览器一致的缓存策略。
补充一点,为什么 NSURLCache 做不到这一点,原因在于,它没有缓存 last-modified,而是缓存上一次请求时间,但服务器判断的是与文件的创建的时间是否相等,所以造成每次都不相等,需要重新下载的情况。
除了上述解决方法,还可以使用 NSURLProtocol 添加监听拦截来作文章,不过这个方法对项目影响太大,个人不建议使用。