iOS 进阶基础

SDWebImage和图片压缩、缓存

2017-10-27  本文已影响1891人  六横六竖亚

iOS中的图片加载

加载方式

imageWithContentsOfFile:+图片路径([[NSBundle mainBundle] pathForResource:@”icon” ofType:@”png”]),不会缓存到内存,适合用于加载较大的不常用的图片降低内存消耗。

imageNamed:+图片名,会缓存到内存且无法释放。

imageWithData:+二进制data,不缓存,适合网络下载图像生成UIImage。

然后将生成的UIImage赋值给UIImageView,CA捕捉到图层树的变化在下一个run loop中提交,然后对图片进行copy操作:解压缩成位图、读到内存中、CA渲染到UIImageView图层。

解压缩

位图其实是一个像素集合,所以图片的文件大小和其解压后所占的内存没有任何关系,只跟图片的像素有关(图片大小:像素宽*像素高*4字节)。但解压缩需要消耗大量CPU时间而且默认在主线程进行。所以出现了,在子线程提前强制解压缩的方案(因为默认的解压缩步骤是在显示到屏幕上的时候才执行的),核心函数:CGBitmapContextCreate。

SDWebImageDecoder中的decodedImageWithImage函数提供了解决方案,原理如下:CGBitmapContextCreate创建一个位图上下文→CGContextDrawImage绘制原始位图到上下文→CGBitmapContextCreateImage创建解压后的新位图。

注:CGContextDrawImage过程中把alpha通道移除了,所以不支持透明度的图片。

图片压缩

对于超大的图片,sd还提供了decodedAndScaledDownImageWithImage压缩方法,避免内存爆炸。原理:将图像矩阵按照规则分割成小型子矩阵进行压缩,然后插值拼接。

SDWebImage

UIImageView+WebCache.h → SDWebImageManager → SDImageCache → SDWebImageDownloader

源码解析

加载:通常我们使用UIImageView显示图片,所以SDWebImage提供了它的扩展方法,一般使用

- (void)sd_setImageWithURL:(nullable NSURL *)url  //路径

placeholderImage:(nullableUIImage *)placeholder  //缺省图

options:(SDWebImageOptions)options  //加载规则

progress:(nullableSDWebImageDownloaderProgressBlock)progressBlock  //进度

completed:(nullableSDExternalCompletionBlock)completedBlock;  //完成的block

或其变种方法进行图片加载(底层依赖UIView+WebCache的扩展方法,更底层是SDWebImageManager的loadImageWithURL方法)。

注:底层流程,SDImageCache的queryCacheOperationForKey方法判断缓存,imageDownloader的downloadImageWithURL方法开启下载任务(方法中利用SDWebImageDownloaderOperation对象配置各种属性以及实现提前子线程解压缩的流程)。

SDWebImageManager

下载图片:核心方法→loadImageWithURL

注:其中还封装了缓存类和下载器的单例;shouldDownloadImageForURL代理设置是否在无缓存时下载;transformDownloadedImage设置是否对图片进行transform操作。

SDImageCache

请求获取缓存:核心方法→queryCacheOperationForKey(一般用url做key),实现流程→imageFromMemoryCacheForKey判断是否在缓存中,self.memCache;diskImageForKey从磁盘中获取,含解码decodedImageWithImage。

生成缓存:核心方法→storeImage: forKey: completion:(可设置是否缓存到硬盘等),实现流程→self.memCache setObject: forKey: cost: 实现内存缓存,storeImageDataToDisk: forKey: 实现磁盘缓存(文件名使用了key即URL的MD5转换)。

缓存清理:核心方法→clearMemory清理内存,clearDiskOnCompletion异步清理磁盘;每次app退出接收到UIApplicationWillTerminateNotification通知时执行deleteOldFilesWithCompletionBlock方法清理过期缓存,若清理完成后磁盘占用大于设定值self.config.maxCacheSize,则按照时间排序(NSURLContentModificationDateKey)后删除。

注:可设置maxMemoryCost最大空间花销或者maxMemoryCountLimit最大数量进行缓存约束;SDImageCacheConfig中控制了maxCacheAge过期时间和maxCacheSize最大缓存空间。

SDWebImageDecoder:

提前解码图片:核心方法→decodedImageWithImage,image.CGImage获取CGImageRef位图,CGColorSpaceRef色彩空间,宽高等,根据前文中的解压缩原理返回一个新的UIImage对象。

压缩图片:核心方法:decodedAndScaledDownImageWithImage,根据属性判断是否需要压缩,然后获取图形上下文和位图,计算像素并分块,然后循环插值。

底层依赖于Quartz 2D的图像处理库。

图片缓存模块

核心功能:根据键值从缓存中获取图片;缓存图片到内存或硬盘;缓存管理机制(过期、清理、限制大小)。

缓存淘汰算法之FIFO

原理:按照时间顺序先进先出,队列。

Second-Chance(FIFO变种1)

原理:给对象加标志位,如果引用了则设置标志位为1。淘汰时判断如果标志位为1,则置为0后加入队列尾部,淘汰一下个为0的对象。

复杂度:需要记录标志位和数据移动,命中率和代价比FIFO高。

Clock(Second-Chance改进版

通过指针实现环形队列避免数据移动,降低代价。

注:FIFO及其变种算法,命中率过低,实际很少使用。

缓存淘汰算法之LRU

原理:最近最少使用。判断最近被使用的时间,最远的优先淘汰。新数据插入链表头部,缓存被命中时移动到链表头部,链表满时丢弃尾部。

命中率:存在热点数据时效率很好但偶发性、周期性的批量操作会导致命中率急剧下降,缓存污染情况严重。

代价:遍历链表找命中的数据块,然后移动到头部。

LRU-K(解决LRU-1的缓存污染)

原理:需要两个队列,首次访问的数据加入到历史队列中,当数据访问次数达到K次时,放入正式的缓存队列中,并删除历史队列中的索引。淘汰数据时,先按照FIFO或者LRU淘汰历史队列中的数据,再按照时间排序淘汰缓存队列中的数据。

命中率:降低了缓存污染问题,命中率提高。但适应性差,K值大时需要大量访问才能将数据移除历史队列,加入缓存。

代价:由于要维护两个队列,所以内存消耗大;由于要基于时间排序,所以CPU消耗高;由于是优先级队列,算法复杂度也较高。

Two queues(2Q,FIFO+LRU)

原理:第一次访问加入FIFO队列中,第二次访问将数据从FIFO移入LRU,两队列各自按照规则淘汰数据。

命中率:高于LRU,代价是FIFO和LRU之和。

缓存淘汰算法之LFU

原理:每个数据块都有一个引用计数,按照引用计数排序,相同按时间。新数据插入到队列尾部,被访问时引用计数+1,队列重新排序。淘汰数据时直接淘汰尾部。

命中率:一般情况下优于LRF,但需要时间适应新的访问模式。

代价:需要记录所有访问数据,内存消耗较高;需要排序,性能消耗较高。

变种:LFU*,只淘汰引用计数为1的数据,无法适应访问模式。

LFU-Aging,考虑访问时间,通过最大访问数来控制访问时间,比如达到100了,就变成50,能更快适应新的访问模式。

Window-LFU,不记录所有历史访问,只记录一段时间内的访问,节约了内存和性能。

上一篇下一篇

猜你喜欢

热点阅读