iOS常见图片缓冲策略

写在前面

“下载图片”几乎是每一个移动App都要处理的问题,对于iOS开发平台而言,下载图片并不是一个多么复杂的事情,给定一个URL,然后使用URL相关库(譬如AFNetworking)去把图片取出来即可,但站在用户的角度,相对于文本信息,下载图片往往会带来更大的成本(下载时间长,流量大等),所以仍然有不少问题需要开发者考虑,譬如本地缓存、URL缓存、服务端压缩与客户端解压等,有些时候还有性能的考量。

笔者目前接触的App开发不是特别多,对性能要求不是特别高,所以着重需要考虑图片流量问题,简而言之,所需要做的事情是尽可能少地通过网络从服务端下载图片,所以本文主要谈谈图片缓存机制。

笔者长时间使用的图片缓存框架是UIImageView(AFNetworking),但并没有直接使用,而是根据自己的需要加以修改,使之更加完善;另外一种倍受好评且使用广泛的的图片缓存框架是SDWebImage,本文也对此缓存机制加以整理,以备后用。

我的图片缓存策略

具体来说,图片缓存一般包括两部分:URL缓存和存储缓存,或曰“内存缓存”和“本地缓存”。前者是将图片存储到内存中,后者是将图片存储到本地磁盘中。因此,当UIImageView控件需要加载一张图片,图片来源可以由三个地方:内存、本地磁盘、网络服务器;消耗成本排序也是:内存 < 本地磁盘 < 网络服务器。

UIImageView(AFNetworking)完成得并不全面,它只做了“URL缓存”,并没有做“存储缓存”,此外,它对“URL缓存”处理比较粗糙。举个栗子,现在App需要下载URL为http://example.com/test.png的图片并“装”到UIImageView控件中,UIImageView(AFNetworking)的处理策略是:

  1. 通过网络下载图片;
  2. 将下载图片存到内存中,存储形式为一个键值对,key为URL,value为下载图片;
  3. 当再次需要加载URL为http://example.com/test.png的图片时,就到内存中把这个图片给找出来,直接“装”到UImageview控件中,而无需通过网络重新下载。

但是在很多场合,在不同的时间同一个URL所对应的图片是不一样的,譬如用户头像,假设其user id为123456,则其头像URL通常为http://example.com/123456/123456.png,用户一般可以自定义头像,所以这个地址的图片常会发生变化…笔者所经历的项目的做法是:当客户端向服务端所要图片信息时,服务器返回的图片信息至少包括两个部分:图片URL和图片的hash值(在服务器里,每个图片都有自己的hash值)。如此这般,客户端在检测图片是否在内存中时不仅仅需要检查URL,还得检查hash值。

总之,针对笔者的项目,UIImageView(AFNetworking)存在两个不足:

  1. “URL缓存”过于粗糙;
  2. 没有“存储缓存”;

笔者针对这两个不足对UIImageView(AFNetworking)进行了加工,最后的缓存策略是:

  1. 根据图片URL和哈希值查找cache是否有这张图片。如果没有,则进入第2步;如果有,则进一步判断图片的hash值和cache中的哈希值是否一样,如果不一样,则进入第2步;
  2. 根据图片哈希值查找物理存储(本地磁盘)是否有这张图片,有则返回图片,没有则进入第3步;
  3. 从网络上下载该图片,下载完后保存到内存和本地磁盘中,前者使用NSCache进行存储,存储信息包括:图片URL、图片哈希值、图片;存储到本地磁盘的图片命名为“图片的哈希值.png”。

SDWebImage的图片缓存策略

SDWebImage是一个很厉害的图片缓存的框架。AFNetworking集成的UIImageView+AFNetworking.h对图片的缓存实际应用的是NSURLCache自带的cache机制。而NSURLCache每次都要把缓存的raw data再转化为UIImage,就带来了数据处理和内存方面的更多操作。具体的比较在这里

SDWebImage提供了如下三个category来进行缓存:

  • MKAnnotationView(WebCache)
  • UIButton(WebCache)
  • UIImageView(WebCache)

以最为常用的UIImageView(WebCache)为例:

  1. setImageWithURL:placeholderImage:options:先显示placeholderImage,同时由SDWebImageManager根据URL来在本地查找图片;
  2. SDWebImageManager:downloadWithURL:delegate:options:userInfo:SDWebImageManager是将UIImageView+WebCache同SDImageCache链接起来的类,SDImageCache:queryDiskCacheForKey:delegate:userInfo:用来从缓存根据CacheKey查找图片是否已经在缓存中;
  3. 如果内存中已经有图片缓存,SDWebImageManager会回调SDImageCacheDelegate:imageCache:didFindImage:forKey:userInfo:;
  4. 而UIImageView+WebCache 则回调SDWebImageManagerDelegate:webImageManager:didFinishWithImage:来显示图片;
  5. 如果内存中没有图片缓存,那么生成NSInvocationOperation添加到队列,从硬盘查找图片是否已被下载缓存;
  6. 根据URLKey在硬盘缓存目录下尝试读取图片文件。这一步是在NSOperation进行的操作,所以回主线程进行结果回调notifyDelegate:;
  7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo:进而回调展示图片;
  8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:;
  9. 共享或重新生成一个下载器SDWebImageDownloader开始下载图片;
  10. 图片下载由NSURLConnection来做,实现相关delegate来判断图片下载中、下载完成和下载失败;
  11. connection:didReceiveData:中利用ImageIO做了按图片下载进度加载效果;
  12. connectionDidFinishLoading:数据下载完成后交给SDWebImageDecoder做图片解码处理;
  13. 图片解码处理在一个NSOperationQueue完成,不会拖慢主线程UI,如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多;
  14. 在主线程notifyDelegateOnMainThreadWithInfo:宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:回调给SDWebImageDownloader;
  15. imageDownloader:didFinishWithImage:回调给SDWebImageManager告知图片下载完成;
  16. 通知所有的downloadDelegates下载完成,回调给需要的地方展示图片;
  17. 将图片保存到SDImageCache中,内存缓存和硬盘缓存同时保存;
  18. 写文件到硬盘在单独NSInvocationOperation中完成,避免拖慢主线程;
  19. 如果是在iOS上运行,SDImageCache在初始化的时候会注册notification到UIApplicationDidReceiveMemoryWarningNotification以及UIApplicationWillTerminateNotification,在内存警告的时候清理内存图片缓存,应用结束的时候清理过期图片;
  20. SDWebImagePrefetcher可以预先下载图片,方便后续使用;

毫无疑问,SDWebImage比UIImageView(AFNetworking)要强大得多,但也因此变得更加复杂了,使用起来麻烦也多了很多;就我认为,对于比较简单的场合,考虑到使用的方便性,UIImageView(AFNetworking)会是一个更好的选择。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值