概述
我们在项目中常说的界面优化,实际上就是减少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
的setNeedsDisplay
layer
调用display
方法- 判断是否需要异步,需要异步将绘制任务添加到队列中
- 绘制完成切回主线程,设置layer的
contents
异步渲染框架:Graver
、YYKit
其他优化方案
- 减少使用
addView
给cell
动态添加view
- 减少图层的图级
- 按需加载:根据需要去加载相关的数据,比如tableview在快速滑动过程中,有些cell是不需要展示,等tableview停止滚动后,加载需要的数据