UIKit并不是一个线程安全的类,UI操作涉及到渲染访问各种View对象的属性,如果异步操作下会存在读写问题,而为其加锁则会耗费大量资源并拖慢运行速度。
另一方面因为整个程序的起点UIApplication是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以view只能在主线程上才能对事件进行响应。
而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上同时更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
从UIKit线程不安全说起
在UIKit中,很多类中大部分的属性都被修饰为nonatomic,这意味着它们不能在多线程的环境下工作,而对于UIKit这样一个庞大的框架,将其所有属性都设计为线程安全是不现实的,这可不仅仅是简单的将nonatomic改成atomic或者是加锁解锁的操作,还涉及到很多的方面:
-
1.假设能够异步设置view的属性,那我们究竟是希望这些改动能够同时生效,还是按照各自runloop的进度去改变这个view的属性呢?
-
2.假设UITableView在其他线程去移除了一个cell,而在另一个线程却对这个cell所在的index进行一些操作,这时候可能就会引发crash。
-
3.如果在后台线程移除了一个view,这个时候runloop周期还没有完结,用户在主线程点击了这个“将要”消失的view,那么究竟该不该响应事件?在哪条线程进行响应?
仔细思考,似乎能够多线程处理UI并没有给我们开发带来更多的便利,假如你代入了这些情景进行思考,你很容易得出一个结论: “我在一个串行队列对这些事件进行处理就可以了。” 苹果也是这样想的,所以UIKit的所有操作都要放到主线程串行执行。
在Thread-Safe Class Design一文提到:
It’s a conscious design decision from Apple’s side to not have UIKit be thread-safe. Making it thread-safe wouldn’t buy you much in terms of performance; it would in fact make many things slower. And the fact that UIKit is tied to the main thread makes it very easy to write concurrent programs and use UIKit. All you have to do is make sure that calls into UIKit are always made on the main thread.
大意为把UIKit设计成线程安全并不会带来太多的便利,也不会提升太多的性能表现,甚至会因为加锁解锁而耗费大量的时间。事实上并发编程也没有因为UIKit是线程不安全而变得困难,我们所需要做的只是要确保UI操作在主线程进行就可以了。
Runloop 与绘图循环
道理我们都懂,那这个究竟跟我们不能在后台线程操作UI有什么关系呢?
UIApplication在主线程所初始化的Runloop我们称为Main Runloop,它负责处理app存活期间的大部分事件,如用户交互等,它一直处于不断处理事件和休眠的循环之中,以确保能尽快的将用户事件传递给GPU进行渲染,使用户行为能够得到响应,画面之所以能够得到不断刷新也是因为Main Runloop在驱动着。
而每一个view的变化的修改并不是立刻变化,相反的会在当前run loop的结束的时候统一进行重绘,这样设计的目的是为了能够在一个runloop里面处理好所有需要变化的view,包括resize、hide、reposition等等,所有view的改变都能在同一时间生效,这样能够更高效的处理绘制,这个机制被称为绘图循环(View Drawing Cycle)。
理解iOS的渲染流程
渲染系统框架
image.png-
UIKit: 包含各种控件,负责对用户操作事件的响应,本身并不提供渲染的能力
-
Core Animation: 负责所有视图的绘制、显示与动画效果
-
OpenGL ES: 提供2D与3D渲染服务
-
Core Graphics: 提供2D渲染服务
-
Graphics Hardware: 指GPU
所以在iOS中,所有视图的实现与动画本质上是由 Core Animation 负责,而不是UIKit。
Core Animation Pipeline 流水线
image.pngCore Animation的绘制是通过Core Animation Pipeline实现,它以流水线的形式进行渲染,具体分为四个步骤:
Commit Transaction:
可以细分为
Layout: 构建视图布局如addSubview等操作
Display: 重载drawRect:进行时图绘制,该步骤使用CPU与内存
Prepare: 主要处理图像的解码与格式转换等操作
Commit: 将Layer递归打包并发送到Render Server
Render Server:
负责渲染工作,会解析上一步Commit Transaction中提交的信息并反序列化成渲染树(render tree),随后根据layer的各种属性生成绘制指令,并在下一次VSync信号到来时调用OpenGL进行渲染。
GPU:
GPU会等待显示器的VSync信号发出后才进行OpenGL渲染管线,将3D几何数据转化成2D的像素图像和光栅处理,随后进行新的一帧的渲染,并将其输出到缓冲区。
Dispaly:
从缓冲区中取出画面,并输出到屏幕上。
知识补充:iOS的VSync与双缓冲机制
网友评论