UIView视图小结

UIView视图小结

前言

日常开发中,最基础的就是UI视图的展示,其中系统是如何找到能响应的视图的呢,为什么有些页面会存在卡顿的现象呢,下面我们就对UIView相关的知识点做下总结。主要有UITableView相关事件传递&视图响应图像显示原理卡顿&掉帧绘制原理&异步绘制离屏渲染、这几个方面。

UITableView相关

UITableView主要会考察:

  • 重用机制
  • 数据源同步:多线程下数据同步

重用机制

相关代码:

cell = [tableView dequeueReusableCellWithIdentifier:identifier];

UI数据源同步

  • 常见于新闻、资讯类的App当中

  • 数据源同步解决方案
  • 这里介绍两种方案:并发数据拷贝和串行访问

  • 第一种:并发访问、数据拷贝

  • 可能存在的弊端:需要记录删除动作并同步;拷贝大量数据,存在内存开销问题

  • 第二种:串行访问

  • 可能存在的弊端:若子线程的预操作耗时长,会出现卡顿的现象

事件传递&视图响应

  • 我们先来看下UIView和CALayer的关系和区别

  • 关系:
    • UIView为其提供内容,以及负责处理触摸等事件,参与响应者链
    • CALayer负责显示内容contents

事件传递(必备)与视图响应链

  • 常见的视图布局

  • 事件传递

  • 主要涉及的函数:
    • -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;:返回值表示哪个视图可以响应事件
    • -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;:判断点击的位置是否在视图范围内
  • 事件传递的流程

  • 如果找不到响应的视图,但是是在UIWindow的范围内,则UIWindow就作为响应事件的视图

  • hitTest:withEvent:的系统实现

  • 在子视图中查找,采用的是倒序查找

  • 应用场景:

    • 方形按钮指定区域接受事件响应
      • 在pointInside判断point是否在区域呢
      • 在hitTest中寻找最终的响应视图

视图事件响应

  • 主要涉及的函数:
      • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
      • (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
      • (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

  • 若响应传递到UIApplication中,仍没有视图处理事件?
    • 忽略这个事件,什么反应都没有

图像显示原理

  • 主要为UI掉帧和卡顿做技术铺垫

  • 一般CPU中会输出的是位图,在GPU中会对位图做图层的渲染,包括纹理的合成;之后将其放入帧缓冲区中。之后视频控制器会提取帧缓冲区的内容,并展示在手机的显示器上。

  • UIView展示内容的过程

  • CPU的工作

  • Layout:UI布局、文本计算
  • Display:绘制(drawRect)
  • Prepare:图片编解码
  • Commit:提交位图

  • GPU渲染管线的内容

UI卡顿、掉帧的原因

  • 先看下画面的渲染时间:

  • 1秒钟有60帧画面更新;16.7ms = 1000ms / 60 卡顿、掉帧的原因:在下一帧画面到来时,CPU和GPU并未在16.7ms内完成画面的合成,于是就导致卡顿、掉帧

滑动优化方案

  • 基于TableView、ScrollView都有哪些优化方案?
  • 从CPU角度:
    • 对象的创建、调整、销毁(放到子线程去处理)
    • 预判版处理(布局计算、文本计算放到子线程去处理)
    • 预渲染(文本等异步绘制,图片编解码等)
  • 从GPU角度:
    • 纹理渲染:尽量避免离屏渲染
    • 视图混合:减少视图层级的复杂性,减轻合成的工作量

UIView的绘制原理(高级考察点)

  • 先看下流程图:

  • setNeedsDisplay:并没有立即进行视图的绘制工作,而是在之后的某一时机才进行视图的绘制工作
    • 相当于在layer上打上了脏标记
    • 在当前RunLoop将要结束的时候,会调用[CALayer display]方法,真正的进行视图的绘制工作
  • 下面我们看下系统绘制的流程

  • Context:视图、控件的上下文

  • 一句话:CALayer内部会创建一个上下文,然后调用[UIView drawRect:]方法,最后将绘制好的位图上传给GPU,这就结束了UI绘制的过程

  • 我们再看下异步绘制的流程:怎样进行异步绘制的

  • 其实是基于系统给我们的方法:需要实现此方法
      • [layer.delegate displayLayer:]
    • 代理负责生成对应的bitmap
    • 设置改bitmap作为layer.contents属性的值
  • 我们看下时序图

  • 一句话:在主队列中设置了setNeedsDisplay方法,再调用CALayer的display方法时,若其代理实现了displayLayer方法,则去全局队列中创建位图,此时主队列正常工作,后面全局队列会返回创建的位图到主队列中,并设置CALayer的contents内容,从而完成绘制

离屏渲染(源于GPU层面)

  • 什么是离屏渲染,怎样去理解离屏渲染

  • 有离屏渲染,就有在屏渲染。我们来看下概念:
    • 在屏渲染:On-Screen Rendering
      • 意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
    • 离屏渲染:Off-Screen Rendering
      • 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
  • 离屏渲染通俗的讲:
    • 离屏渲染起源于GPU层面,当我们设置某一项UI视图的图层属性,视图的效果不能直接呈现给屏幕,而需要在别的地方做额外的处理预合成,这就会触发离屏渲染
  • 何时会触发离屏渲染?
    • 设置圆角(当和maskToBounds一起使用时)
    • 图层蒙版
    • 视图的阴影
    • 光栅化
  • 为何要避免离屏渲染?
    • 在触发离屏渲染的时候,会增加GPU的工作量,很可能会导致CPU、GPU总耗时超出一帧的时间,可能就会导致UI的卡顿和掉帧,所以要避免离屏渲染。

参考文章

最后

如果对大家有帮助,请github上follow和star,本文发布在戴超的技术博客,转载请注明出处

Loading Disqus comments...
Table of Contents