imageNamed
通过 imageNamed 创建 UIImage 时,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。
当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,解码之后再渲染(解码一般都是在主线程)同时解码结果会保存到一个全局缓存去。
据我观察,在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。
- 问题:那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出
- 原因:既然图片的解压缩需要消耗大量的 CPU 时间,那么我们为什么还要对图片进行解压缩呢?是否可以不经过解压缩,而直接将图片显示到屏幕上呢?答案是否定的。
在将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因
不管是 JPEG 还是 PNG 图片,都是一种压缩的位图图形格式。位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点。像素为 30 × 30 ,文件大小为 843B 的 PNG 图片解压缩后的大小是 3600B ,是原始文件大小的 4.27 倍。解压缩后的图片大小与原始文件大小之间没有任何关系,而只与图片的像素有关。
解压缩后的图片大小 = 图片的像素宽 30 * 图片的像素高 30 * 每个像素所占的字节数 4
因此,在将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因。
- 解决方案
当未解压缩的图片将要渲染到屏幕时,系统会在主线程对图片进行解压缩,而如果图片已经解压缩了,系统就不会再对图片进行解压缩;而强制解压缩的原理就是对图片进行重新绘制,得到一张新的解压缩后的位图。其中,用到的最核心的函数是 CGBitmapContextCreate
这里也是参考了SDWebImage的图片编解码的思路,SD在拿到图片data的时候并没有将它直接转为image对象,而是 在子线程里面对图片进行重新绘制,得到一张新的解压缩后的位图, 这样被解码的图片就赋值给imageView的时候就不会再进行解码,也就不会妨碍主线程了
#import "SDImageCoderHelper.h"
调用+ (UIImage * _Nullable)decodedImageWithImage:(UIImage * _Nullable)image;方法
- 分块绘制
- 显示缩略图
- 尽量避免设置alpha
屏幕上每个像素点的颜色是由当前像素点上的多层layer(如果存在)共同决定的,GPU会进行混合计算出混合颜色的RGB值,最终显示在屏幕上。而这需要让GPU计算,所以我们要尽量避免设置alpha,这样GPU会忽略下面所有的layer,节约计算量
- 不要出现image size与imageView size不同的情况,这样会触发反锯齿计算,增加性能损耗
实际开发中,本地的图片比较好把控,只需要写好对应的尺寸就好了,但是对于download下来的图片,可以在加载完后进行size处理,以满足imageView frame。特别是对于很多app,有大量的tableview,如果进行处理,则会大幅度提高流畅度
圆角的处理及对比
- cornerRadius + layer.masksToBounds
iOS9后优化了 cornerRadius 的绘图方式,不再会触发离屏渲染
简单圆角场景下推荐使用 - 用贝塞尔曲线 (性能比较差)
- 重新绘制圆角 (性能最好)
YYWebImage为例,可以先下载图片,再对图片进行圆角处理,再设置到cell上显示
UIImage+YYAdd
imageByRoundCornerRadius:
位图尺寸很大,数量很多时要注意内存警告,最好配合缓存机制使用,避免因内存溢出而崩溃
- 混合图层,用一张镂空的透明图片作遮罩
- 服务器返回圆角图片
参考
https://juejin.im/post/5c15d04d6fb9a049cb18a435
https://juejin.im/post/584f9e47ac502e006938e9cf
https://juejin.im/post/5c05f775f265da614d08f1ae
网友评论