概述
我们在项目中常说的界面优化,实际上就是减少UI的卡顿,让用户操作界面的时候感觉更顺畅。那么如和减少UI卡顿呢,下面就来看一看
卡顿的原因
下面先用一张图来了解视图的显示时如何操作的
BUS:是总线用来传输数据CPU:负责需要渲染的数据进行计算GPU:负责渲染,把需要渲染的数据输出到framebuffer(帧缓冲区)framebuffer再输出到Video Controller控制,最终于Monitor显示
掉帧
- 上面是早期的一种方式,由于cpu计算比较耗时,在某段时间屏幕上就会显示空白,为了解决这个问题,引入了
双缓冲机制,一个前帧和一个后帧,不断的从两个缓冲区交替读取数据,避免了显示空白的问题。
时间上卡顿的最主要原因就是因为掉帧,下面用一张图来展示掉帧的原理
- 垂直同步信号的时间间隔是
16.67ms,一般来说人看到的流畅效果如果是1秒钟能够有60帧,那没就会看起来比较流畅了。那么显示一帧刚好需要1/60=16.67ms - 在两个
VSync垂直信号之间需要完成一帧的渲染,如果在这段时间cpu没有准备好一帧,屏幕就无法显示,就会出现掉帧
卡顿的检测
下面主要介绍几个比较常用的检测卡顿的三方库,并讲解一下其中的原理
FPS检测卡顿
这个主要是利用了系统的CADisplayLink,YYkit里面也提供了这个功能。
CADisplayLink实际上是一个定时器绑定到了垂直同步信号上
在触发方法
tick中,会记录上一次的时间,并判断两次时间间隔是否大于等于1秒,如果大于等于1秒,计算这段时间内,该方法被执行的次数fps。所以如果fps接近60,说明流程度很好。
Runloop检测卡顿
Runloop 检测卡顿的demo
- 首先新建一个QHMonitor的类,
- 添加runloop观察者
- 开始监测
- 如果runloop在休眠,没有信号,
dispatch_semaphore_wait等待1s后st返回1,timeout = 0 runloop开始处理事物,接收到observer,发送信号,dispatch_semaphore_wait立即处理返回,st为0. 如果事物处理时间比较长,dispatch_semaphore_wait返回1 ,st != 0,runloop状态也没有发生变化,这样++timeoutCount,单连续2次超时,说明出现了卡顿timecount = 0,继续监测下次卡顿。
界面优化实操
上面主要讲解了卡顿的原因,以及如何检测卡顿。但是如何解决这些问题呢?
预先排版
比如一个比较复杂的cell,里面有很多空间,需要布局。我们可以拿到数据之后,异步处理布局
- 新建一个layout类,里面存储布局信息
- 多线程计算处理布局信息,这样就可以避免在滑动cell的时候计算布局信息
对于一些复杂的界面,或者样式有多种形态,不太建议用xib。
预解码和预渲染
- 解码一般用在视频和图片比较多
- 图片的加载流程一般是
cpu拿到定点数据和纹理进行解码产生位图,然后传递给gpu进行渲染 - 如果图片比较大的情况下进行解码操作,就会占用主线程,造成UI卡顿
- 为了解决这一问题,我们需要将解码的操作放到子线程中去处理
在我们平常的开发中使用的UIImage,并不是真正的图片数据,而是一个模型
UIImage的二进制流存储在DataBuffer中,经过decode解码,加载到imageBuffer中,最终进入FrameBuffer才能被渲染
为什么图片需要预解码?
- 我们平常会这样在子线程处理图片,但实际上解码的操作还是在主线程。self.imageView.image 赋值的时候会进行解码。 -所以如果想要优化这一块,需要重写预解码这一块。第三方框剪sdwebimage,就做了处理。
在SDWebImageDownloaderOperation.m文件中,将解码操作加入异步队列
将二进制流数据转为CGImageSourceRef
生成UIimage图片
所以SDWebImage的操作:
网络获取图片二进制数据->子线程预解码->回调主线程显示
异步渲染
UIView 和CALayer之间的区别?
UIView基于UIKit框架,CALayer基于CoreAnimation框架UIView负责界面布局和子视图的管理,CALayer只负责显示,而且显示的是位图UIView可以处理用户触摸事件,CALayer不能处理用户触摸事件CALayer继承于NSObject,而UIView继承于UIResponder,所以UIVIew相比CALayer多了事件处理功能- 从底层来说,
UIView属于UIKit的组件,而UIKit的组件到最后都会被分解成layer,存储到图层树中 UIView,不需要交互时,使用两者都可以
UIView更加偏向于与用户交互行为,Layer是来用渲染的,iOS层级渲染图如下:
什么是异步渲染?
异步绘制,就是可以在子线程把需要绘制的图形,提前在子线程处理好。将准备好的图像数据直接返给主线程使用,这样可以降低主线程的压力
异步绘制流程图:
UIView触发layoutSubviews,或者主动调用layer的setNeedsDisplaylayer调用display方法- 判断是否需要异步,需要异步将绘制任务添加到队列中
- 绘制完成切回主线程,设置layer的
contents
异步渲染框架:Graver、YYKit
其他优化方案
- 减少使用
addView给cell动态添加view - 减少图层的图级
- 按需加载:根据需要去加载相关的数据,比如tableview在快速滑动过程中,有些cell是不需要展示,等tableview停止滚动后,加载需要的数据