SDWebImage和图片压缩、缓存

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,不记录所有历史访问,只记录一段时间内的访问,节约了内存和性能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容

  • 绘制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一个像素是如何绘制到屏幕上去的?有很多...
    阿狸旅途T恤阅读 1,633评论 0 7
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,046评论 25 707
  • 知道了那么多关于iOS上界面渲染的理论知识后,终于可以回归最开始的问题,将一张 png/jpg 格式的图片渲染到页...
    巫师学徒阅读 707评论 0 2
  • 出错返回-1,调用成功就不返回了,如果调用成功,其后面的语句都不会再执行了,因为新程序替换了当前进程的正文等
    WinddddRunner阅读 716评论 0 0
  • 我终于决定给自己再一次机会 我终于相信命运的垂青 我终于认可想要的只有自己能给 我终于决定再一次前行, 不到300...
    病小喵阅读 231评论 0 0