09.渲染原理及优化
Last updated
Last updated
将图形渲染到屏幕是
CPU
和GPU
协同完成的一个过程.
CPU
计算视图Frame
图片解码
将需要绘制的纹理图片通过数据总线传递给GPU
GPU
纹理混合
顶点变换与计算
像素点的填充计算
渲染到缓冲区
时钟信号
垂直同步信号V-Sync
水平同步信号H-Sync
iOS
设备双缓冲机制
显示系统通常会引入两个帧缓冲区
有前帧缓存、后帧缓存,即GPU会预先渲染好一帧放入一个缓冲区内(前帧缓存),让视频控制器读取
当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器(后帧缓存)
当你视频控制器已经读完一帧,准备读下一帧的时候
GPU会等待显示器的VSync信号发出后,前帧缓存和后帧缓存会瞬间切换
后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存
如果在一个VSync
时间内CPU
或GPU
没有将处理完成的数据提交到帧缓冲区,那一帧就会被丢弃
按照
60fps
刷新率,每隔16ms就会又一次VSync
信号
尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
Autolayout会比直接设置frame消耗更多的CPU资源
图片的size最好刚好跟UIImageView的size保持一致
控制一下线程的最大并发数量
尽量把耗时的操作放到子线程
尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
尽量减少视图数量和层次
减少透明的视图(alpha<1),不透明的就设置opaque为YES
尽量避免出现离屏渲染
YYFPSLabel
基于 DisplayLink
每次++,少了就掉帧了
Runloop
在 主线程
的 Runloop
中创建一个 Observer
监听事务
在 子线程
开启计时
两次事务的间隔超过一定的时间就标识有事务比较耗时
微信的 Matrix
也是基于 Runloop
的,更全面
滴滴 Doraemon
主线程发不断发信号
使用后会有缓存,适合重复使用的场景
When searching the asset catalog, this method prefers an asset containing a symbol image over an asset with the same name containing a bitmap image. Because symbol images are supported only in iOS 13 and later, you may include both types of assets in the same asset catalog. The system automatically falls back to the bitmap image on earlier versions of iOS. You cannot use this method to load system symbol images; use the init(systemName:) method instead.
This method checks the system caches for an image object with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method creates the image from an available asset catalog or loads it from disk. The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used.
In iOS 9 and later, this method is thread safe.
不会创建缓存,适合于低频使用的资源图
This method does not cache the image object.
将UIImage
赋值给UIImageView
一个隐式的CATransaction
捕捉到了UIImageView
图层树的变化
主线程下个RunLoop
到来时,CoreAnimation
提交了这个Transaction
这个过程可能会对图片进行Copy
操作,而受图片是否字节对齐
等因素的影响,这个Copy
操作可能包含一下部分或全部操作:
分配内存缓冲区,用于管理文件IO
和解压缩操作
将图片数据从磁盘读取到内存
将图片数据解码成未压缩的位图格式,这是个非常耗时的CPU
操作
CPU
计算好图片Frame
,对图片解压
后,交给GPU
进行渲染
CoreAnimation
的CALayer
用位图数据渲染UIImageView
位图就是一个像素数组,包含了每个像素点的信息。
JPG
和PNG
都是压缩的位图
JPG
有损压缩
PNG
无损压缩,支持alpha
通道
提前进行相关计算,例如在 Cell
复用之前就通过数据模型对相关 Layout
信息进行计算。否则每次复用都进行计算就很浪费性能了。
系统默认会在主线程对图片进行解压,但该操作是个非常耗时的操作。所以现在就出现了对图片进行异步解压的方案。核心方法是CGBitmapContextCreate
。
SDWebImage
可以在sd_decompressedImageWithImage
处断点调试过程
下面是我在我的个人项目 梦见账本 中的账户封面设置中用做的优化:
一般我们卡顿主要是因为列表有大量的图片,我们可以选择在滚动的时候采用展示展位图,滚动停止的时候再展示相关图片的方式来提高性能。
当然这样对于用户的浏览体验来说会有一些影响。
UIView 和 CALayer 的关系
Graver
美团开源Graver框架:用“雕刻”诠释iOS端UI界面的高效渲染
Graver 好像是借鉴了 YYAsyncLayer 有争议,后来取消开源了。