这篇文章介绍 UI视图 相关的内容,所涉及的知识点目录如下:
一、UITableView 相关 二、事件传递 & 视图响应 三、图像显示原理 四、卡顿 & 掉帧 五、绘制原理 & 异步绘制 六、离屏渲染
cell = [tableView dequeueReusableCellWithIdentifier:Identifier]
真正被重用的时候,才会调用 [cell prepareForReuse] 方法,cell 消失并不会调用 prepareForReuse 。
代码实现重用原理,基本原理是通过两个 NSMutableSet:
如果确定一个 cell 是一定会被创建的,那么可以提前对 Identifier 进行 registerClass 的操作,这样通过 dequeueReusableCellWithIdentifier: 取出来的 cell 一定是不为 nil 的。
在实际开发过程中,tableView 数据源的同步是一大令人头疼的问题,追加数据、变更数据、删除数据都有可能导致 tableView crash,所以处理好 数据源同步问题 ,是掌握 tableView 的关键。
一个最常见的crash问题是 数据源变更 没有放到 主线程,导致 tableView UI 刷新时, 数据源已经不是触发 UI 刷新时的数据,导致数据不匹配 crash。
那么数据源同步有哪两种方案呢?
以下面这个 case 为例:
大致解释一下这里的情况:
子线程正在进行网络请求操作,比如正在上传一个视频,但上传过程中用户把这个视频给删除了,因为上传是异步的,我们基本不可能在清理上传数据时,取消掉上传的网络请求。
如果这时网络回包上传成功了,那么就会把数据抛给主线程,重新刷新 UI ,这样就会把刚刚我们实际上已经删除的数据又加回来了。
上面这个问题在处理发表流程中非常常见,是一个基本必须处理的坑,那么这个问题要怎么处理呢?大家看一下下面的流程图:
清楚怎么处理了吗?
就是在用户删除本地数据的时,把这个数据拷贝一份塞到 deleteArray 中,等到网络请求回包后,这个数据能不能展示,需要经过 deleteArray 的过滤。
串行的思路很简单,就是在用户手动处理数据变更时,必须等到子线程操作完成,才能进行下一步操作。
并行方案 和 串行方案 有着不同的应用场景,一般来说:
UIView = CALayer + 响应链
展示布局实际上是 CALayer.contents 决定的,contents对应一个位图。
你可能会说 UIView 也有 BackgroundColor 属性,那又是为什么呢?
实际上 UIView.backgroundColor == CALayer.backgroundColor ,也就是说 UIView.backgroundColor 是对 CALayer.backgroundColor 同名属性方法的一个封装。
通过单一设计原则,明确了 UIView 和 CALayer 的分工。
谁来响应手势,和我喊了一个人的名字,谁来响应是一个事情:
首先是某一篇区域的人听到我喊了这个人的名字,然后是这个人自己来回应我。
所以手势响应需要两个步骤:明确手势的响应区间 >> 决定哪个元素来响应。
所以也就对应着两个方法:
(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
那这里实际上存在一个问题:pointInside: withEvent: 响应顺序是如何呢?
类比到我喊人的名字,但有8个区域,那这8个区域按照什么样的顺序判断是否响应我呢?
- 通过加入到视图上的顺序,「倒序」遍历视图的 hitTest:withEvent: 的方法
- 如果 hitTest:withEvent: 返回的是 nil ,那么就按顺序继续「倒序」遍历,直到没有可响应的对象为止。
那你可能好奇了,你说的这个顺序中并没有说到 pointInside:withEvent: ,它又有什么用呢?
hitTest:withEvent: 底层会调用 pointInside:withEvent: 方法,判断手势在不在控件上,如果你已经优先实现了 hitTest:withEvent: ,那么就不会再调用 pointInside:withEvent: 方法了,可以理解为一个控件只需要实现其中一个方法,就能达到自定义事件传递的效果。
main() 函数中执行了 UIApplicationMain() 函数:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
那 UIApplication 有什么用呢?
UIApplication 是应用程序的象征,每一个 App 都有自己的 UIApplication 对象,而且是单例:
通过[UIApplication sharedApplication]可以获得这个单例对象,这也是 iOS 程序启动后创建的第一个对象。
通过 UIApplication 对象,我们可以进行一些系统级别的操作:
那 UIApplication 和 UIApplicationDelegate 之间有什么关系呢?
1.UIApplication 是 App 和 系统 之间通信的一个接口
2.所有 ViewController 默认遵守了 UIApplicationDelegate
3.一旦有系统级别的消息要通知当前页面,通过 UIApplication 通知它的 delegate 对象,让 delegate 对象来响应这些系统事件,包括:
CPU 和 GPU 两个硬件是通过总线连接起来的,CPU将计算后的「bitmap」经过「总线」输出给「GPU」
GPU拿到「bitmap」后,进行图层渲染,将生成的结果放到「frame buffer」中
最后由硬件控制器根据 sync 信号,在指令之前去 「frame buffer」提取显示内容,最终显示到屏幕上。
举例:
绘制好的内容最终会通过 Core Animation 框架 提供给 GPU OpenGL 渲染管线。
60fps,也即60帧,也即16.7ms就要产生一帧动画,也就是在这 16.7ms 内CPU和GPU协同产出一帧数据,如果没有按时产出,那就会导致掉帧。
为什么是60fps ? 这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新
把不必要的工作放到子线程去做,让 CPU 有更多精力和时间去响应用户的交互。
关键方法:displayLayer
[layer.delegate displayLayer:]
当我们设置某一些 UI 视频时,如果指令是在「未全部合成」之前不能展示的,那么就触发了「离屏渲染」,常见的比如:圆角(当和maskToBounds一起使用)、蒙层、遮罩等,
离屏渲染 的概念起源于 GPU 层面,指的是:GPU在当前屏幕缓冲区以外新开辟了一个缓冲区进行渲染操作。
触发离屏渲染时,毫无疑问会增加GPU的工作量,而增加GPU的工作量,可能会导致 CPU+GPU 生成单个帧的总工作时间加起来超过 16.7ms, 会导致掉帧或卡顿。而且: