《新浪微博剖析 iOS 高级面试》笔记(一):UI视图相关面试问题
UITableView相关
一、重用机制
1、iOS如何实现cell的重用机制?

A1-A7使用相同的identifer,当tableView向上滑动,A1划出页面后,就被放入了重用池。- 当
A7即将展示时,首先会在重用池中查看时候有相同identifer的cell可以被重用,如果有则直接取出使用,若无则创建一个新的cell。
2、如何手动实现重用机制?
ViewReusePool类的声明
ViewReusePool类的实现
dequeueReusableView函数实现
addUsingView:函数实现
reset函数实现
ViewReusePool类的使用
二、数据源同步问题
- 当数据源在
主线程中有删除操作,同时在子线程上又有加载更多数据的操作时,就会出现数据源同步问题。
1、数据源同步解决方案
a、并发访问、数据拷贝
子线程返回主线程的数据中,仍然包含删除的这一条数据。
- 在
主线程进行删除操作时,将操作记录下来。之后在子线程同步数据时,同步删除操作。
b、串行访问
- 将
子线程的数据同步和主线程的删除操作全部放入一个串行队列中执行。 - 删除动作可能会有延时。

事件传递&视图相应
一、UIView和CALayer
1、UIView和CALayer的关系和区别?
a、关系

UIView对象中的layer指向一个CALayer变量UIView对象中的backgroundColor属性,是对CALayer同名属性的封装。UIView展示部分是由CALayer中的contents来决定。contents对应的backing store其实是一个bitmap的位图。
b、区别
UIView为其提供内容,以及负责处理触摸等事件,参与响应链。CALayer负责显示内容contents。
2、为什么UIView负责触摸事件,CALayer负责显示?
- 设计模式,
单一职责原则。
二、事件传递与视图响应链
1、当点击View C2区域,系统是如何找到响应视图的呢?

a、事件传递的流程

- 当用户点击屏幕,事件会被
UIApplication接受,并传递给UIWindow。 UIWindow调用hitTest函数,在hitTest内调用pointInside判断事件是否在该视图内。- 若为
false,则返回该视图,事件传递流程结束。 - 若为
true,则可倒叙遍历该视图的子视图,并调用子视图的hitTest函数。 - 找到最终
hitTest为true的子视图,并依次返回,事件传递流程结束。
b、hitTest系统内部实现

- 在当前视图子视图调用
hitTest函数前,需要将当前坐标转换为子视图中的坐标。
2、如何只让方形图片的圆形区域接受事件响应?
- 重写视图的
pointInside函数,使得点击区域在圆形范围内返回true,否则返回false。
3、视图响应流程
a、事件的响应是通过响应链来传递的。

UIView通过继承UIResponder,拥有以下函数
b、事件传递之后由谁来响应?

- 如果响应视图无法处理响应事件,则响应事件会通过
响应链传递给父视图尝试处理,直到传递给UIApplication。 如果传递给UIApplication依然没有处理响应事件,则事件将被忽略。
图像显示原理
一、图像显示流程

CPU和GPU是通过事件总线链接在一起的。CPU输出的位图,在适当时机由事件总线上传给GPU。GPU会对位图进行渲染,然后将结果放入帧缓冲区。视频控制器通过Vsync信号,在指定时间(16.7ms)之前,从帧缓冲区中提取屏幕显示内容,然后显示在显示器上。
二、UI视图显示过程

- 当创建一个
UIView对象,它的显示部分由CALayer来控制的。 CALayer有一个contents属性,就是最终绘制到屏幕上的位图。- 在绘制
contents内容时,系统会回调drawRect:函数,我们可以在函数中增加绘制内容。 - 绘制好的
位图会通过Core Animation框架,最终经由GPU当中的OpenGL渲染管线,渲染在屏幕上。
三、CPU工作过程

1、Layout
- UI布局(
frame设置) - 文本计算(
size计算)
2、Display
- 绘制(
drawRect:)
3、Prepare
- 图片编解码
4、Commit
- 提交位图
四、GPU渲染管线过程

卡顿&掉帧的原因

- 按照每秒
60FPS刷新率,每隔16.7ms就会有一次Vsync信号。 - 在
16.7ms内,需要CPU和GPU协同产生这一帧的画面,并在下一次Vsync信号来临时,显示这一帧的画面。 - 如果
CPU和GPU的工作时长超过16.7ms,那么当Vsync信号来临时,无法提供这一帧的画面,就会出现掉帧现象。 - 上一帧没有显示的画面,会在下一次
Vsync信号来临时显示。
一、滑动优化方案
1、CPU
- 对象创建、
调整、销毁可以放在子线程。 - 预排版(布局计算、文本计算)操作,可以放在子线程操作。
- 预渲染(文本等
异步绘制、图片编解码等)操作,降低CPU的耗时。
2、GPU
- 避免
离屏渲染,降低纹理渲染的耗时。 - 如果
视图层级复杂,GPU在视图合成时会做大量的计算。可以通过异步绘制等机制,减少视图层级,减轻GPU的压力。
绘制原理&异步绘制
一、UIView的绘制原理

- 当调用
setNeedsDispaly函数,实际是调用view.layer的setNeedsDispaly函数。 - 该函数会将
layer标记,在runloop即将结束时,调用CALayer display函数,进入当前视图的真正绘制。 - 在
CALayer display函数中,会判断它的代理是否响应displayLayer:函数,如果YES,则可进行异步绘制,否则进入系统绘制流程。
二、系统的绘制流程

layer会创建一个backing store,在drawRect:函数中可以拿到这个上下文。layer会判断是否有代理,如果有代理,在系统内部绘制完成后,会调用UIView drawRect:,允许在系统绘制基础上,进行增添修改。- 最终由
CALayer上传backing store到GPU。
三、异步绘制
- 如果
layer存在代理,则由代理执行display:函数生成位图,并设置该bitmap作为layer.contents属性。
离屏渲染
在屏渲染(On-Screen Rendering)意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示屏幕缓冲区中进行。离屏渲染(Off-Screen Rendering)意为离屏渲染,指的是GPU的再当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
一、什么场景会触发离屏渲染?
- 圆角(需要和maskToBounds一起使用)
- 图层蒙版
- 阴影
- 光栅化
二、为什么要避免离屏渲染?
- 创建新的渲染
缓冲区,会有内存上的开销。 - 多通道渲染管线,最终需要合成,会涉及
上下文切换,增加GPU的开销。 - 总结:离屏渲染会增加
GPU的处理时间,这样可能导致CPU+GPU的总处理时间超过16.7ms,从而出现掉帧卡顿的现象。
UI视图面试总结
- 系统的UI事件传递机制是怎样的?
- 考察
hitTest和pointInside内部实现。
- 考察
- 使UITableView滚动更流畅的方案或思路都有哪些?
CPU方面,在子线程进行对象的创建、调整、销毁、预排版、图片异步绘制。
- 什么是离屏渲染?
- 在当前屏幕缓存区外,新开辟一个缓冲区进行渲染。
- UIView 和 CALayer之间的关系是怎样的?
UIView负责事件传递和事件响应。CALayer负责UI视图显示。- 使用到设计模式六大设计原则中的
单一职责原则。
转载自:https://juejin.cn/post/6899772676794122253