阅读 269

iOS界面优化

概述

我们在项目中常说的界面优化,实际上就是减少UI的卡顿,让用户操作界面的时候感觉更顺畅。那么如和减少UI卡顿呢,下面就来看一看

卡顿的原因

下面先用一张图来了解视图的显示时如何操作的

显示原理.png

  • BUS:是总线用来传输数据
  • CPU:负责需要渲染的数据进行计算
  • GPU:负责渲染,把需要渲染的数据输出到framebuffer(帧缓冲区)
  • framebuffer再输出到Video Controller控制,最终于Monitor显示

掉帧

  • 上面是早期的一种方式,由于cpu计算比较耗时,在某段时间屏幕上就会显示空白,为了解决这个问题,引入了双缓冲机制一个前帧一个后帧,不断的从两个缓冲区交替读取数据,避免了显示空白的问题。

时间上卡顿的最主要原因就是因为掉帧,下面用一张图来展示掉帧的原理

垂直信号.png

  • 垂直同步信号的时间间隔是16.67ms,一般来说人看到的流畅效果如果是1秒钟能够有60帧,那没就会看起来比较流畅了。那么显示一帧刚好需要1/60 = 16.67ms
  • 在两个VSync垂直信号之间需要完成一帧的渲染,如果在这段时间cpu没有准备好一帧,屏幕就无法显示,就会出现掉帧

卡顿的检测

下面主要介绍几个比较常用的检测卡顿的三方库,并讲解一下其中的原理

FPS检测卡顿

这个主要是利用了系统的CADisplayLinkYYkit里面也提供了这个功能。

截屏2021-10-08 下午3.14.46.png

  • CADisplayLink实际上是一个定时器绑定到了垂直同步信号上

截屏2021-10-08 下午3.17.58.png 在触发方法tick中,会记录上一次的时间,并判断两次时间间隔是否大于等于1秒,如果大于等于1秒,计算这段时间内,该方法被执行的次数fps。所以如果fps接近60,说明流程度很好。

Runloop检测卡顿

Runloop 检测卡顿的demo

  • 首先新建一个QHMonitor的类,
  • 添加runloop观察者

截屏2021-10-08 下午3.50.52.png

  • 开始监测

截屏2021-10-08 下午3.54.56.png

  1. 如果runloop在休眠,没有信号,dispatch_semaphore_wait等待1s后st返回1,timeout = 0
  2. runloop开始处理事物,接收到observer,发送信号,dispatch_semaphore_wait 立即处理返回,st为0. 如果事物处理时间比较长,dispatch_semaphore_wait 返回1 ,st != 0,runloop状态也没有发生变化,这样++timeoutCount,单连续2次超时,说明出现了卡顿
  3. timecount = 0 ,继续监测下次卡顿。

界面优化实操

上面主要讲解了卡顿的原因,以及如何检测卡顿。但是如何解决这些问题呢?

预先排版

比如一个比较复杂的cell,里面有很多空间,需要布局。我们可以拿到数据之后,异步处理布局

  • 新建一个layout类,里面存储布局信息

截屏2021-10-08 下午5.35.12.png

  • 多线程计算处理布局信息,这样就可以避免在滑动cell的时候计算布局信息

对于一些复杂的界面,或者样式有多种形态,不太建议用xib。 截屏2021-10-08 下午5.36.05.png

预解码和预渲染

- 解码一般用在视频和图片比较多

  • 图片的加载流程一般是cpu拿到定点数据和纹理进行解码产生位图,然后传递给gpu进行渲染
  • 如果图片比较大的情况下进行解码操作,就会占用主线程,造成UI卡顿
  • 为了解决这一问题,我们需要将解码的操作放到子线程中去处理

在我们平常的开发中使用的UIImage,并不是真正的图片数据,而是一个模型

截屏2021-10-09 上午9.50.14.png

UIImage的二进制流存储在DataBuffer中,经过decode解码,加载到imageBuffer中,最终进入FrameBuffer才能被渲染

截屏2021-10-09 上午9.50.28.png

为什么图片需要预解码?

截屏2021-10-09 上午11.11.17.png

  • 我们平常会这样在子线程处理图片,但实际上解码的操作还是在主线程。self.imageView.image 赋值的时候会进行解码。

-所以如果想要优化这一块,需要重写预解码这一块。第三方框剪sdwebimage,就做了处理。

SDWebImageDownloaderOperation.m文件中,将解码操作加入异步队列

截屏2021-10-09 上午11.20.05.png

将二进制流数据转为CGImageSourceRef

截屏2021-10-09 上午11.26.15.png 生成UIimage图片 截屏2021-10-09 上午11.24.34.png 截屏2021-10-09 上午11.24.47.png 所以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层级渲染图如下:

截屏2021-10-09 下午3.12.45.png

什么是异步渲染?

异步绘制,就是可以在子线程把需要绘制的图形,提前在子线程处理好。将准备好的图像数据直接返给主线程使用,这样可以降低主线程的压力

异步绘制流程图:

截屏2021-10-09 下午3.58.40.png

  • UIView触发layoutSubviews,或者主动调用layersetNeedsDisplay
  • layer调用display方法
  • 判断是否需要异步,需要异步将绘制任务添加到队列中
  • 绘制完成切回主线程,设置layer的contents

异步渲染框架:GraverYYKit

其他优化方案

  • 减少使用addView 给cell动态添加view
  • 减少图层的图级
  • 按需加载:根据需要去加载相关的数据,比如tableview在快速滑动过程中,有些cell是不需要展示,等tableview停止滚动后,加载需要的数据
文章分类
iOS
文章标签