2021疑难解惑iOS面试题集合ios多线程

iOS - 为什么要在主线程中操作UI

2020-11-21  本文已影响0人  Fat_Blog

在开发中,如果我们在后台线程中对UI进行操作,比如imageView.image = image;那么编译器就会弹出一个runtime错误,这时,我们只需要把这一行代码放到主线程中执行,那就可以解决问题了,但是为什么要在主线程中操作UI才是正确的呢?

一般分为三个原因

先从UIKit线程不安全说起

在UIKit中,很多类中大部分的属性都被修饰为nonatomic,这意味着它们不能在多线程的环境下工作,而对于UIKit这样一个庞大的框架,将其所有属性都设计为线程安全是不现实的,这可不仅仅是简单的将nonatomic改成atomic或者是加锁解锁的操作,还涉及到很多的方面:

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 与绘图循环

解释上面三个原因中的第二个原因 :

UIApplication在主线程所初始化的Runloop我们称为Main Runloop,它负责处理app存活期间的大部分事件,如用户交互等,它一直处于不断处理事件和休眠的循环之中,以确保能尽快的将用户事件传递给GPU进行渲染,使用户行为能够得到响应,画面之所以能够得到不断刷新也是因为Main Runloop在驱动着。

而每一个view的变化的修改并不是立刻变化,相反的会在当前runloop的结束的时候统一进行重绘,这样设计的目的是为了能够在一个runloop里面处理好所有需要变化的view,包括resize、hide、reposition等等,所有view的改变都能在同一时间生效,这样能够更高效的处理绘制,这个机制被称为绘图循环(View Drawing Cycle)

因为点击等用户交互事件是由系统传递给UIApplication中,并在Main Runloop中进行处理与响应。这时假设UI可以在后台线程中进行处理,而Main Runloop并没有收集到这个UI的相关事件,那么当刷新画面时,有可能出现画面刷新完,但是UI在后台线程中的Runloop还没结束,即这个UI还没有被渲染,那么就会出现点击没反应,这时用户应该是在吐槽你的app了

iOS的渲染流程

解释第三个原因

渲染框架

image.png

在iOS中,所有视图的现实与动画本质上是由 Core Animation 负责,而不是UIKit。

Core Animation Pipeline 流水线

image.png

Core Animation的绘制是通过Core Animation Pipeline实现,它以流水线的形式进行渲染,具体分为四个步骤:

VSync:

VSync(vertical sync)是指垂直同步,在玩游戏的时候在设置的时候应该会看见过这个选项,这个机制能够让显卡和显示器保持在一个相同的刷新率从而避免画面撕裂。在iOS中,屏幕具有60Hz的刷新率,这意味着它每秒需要显示60张不同的图片(帧),但GPU并没有一个确定的刷新率,在某些时候GPU可能被要求更强力的数据输出来确保渲染能力,这时候他们可能比屏幕刷新率(60Hz)更快,就会导致屏幕不能完整的渲染所有GPU给他的数据,因为它不够快,屏幕的上一帧还没渲染完,下一帧就已经到来了,这就导致画面的撕裂。
这个时候我们就要引入VSync了,简单来说它就是让显卡保持他的输出速率不高于屏幕的刷新率,启用了VSync后,GPU不再会给你可怜的60Hz屏幕每秒发送100帧了,它会增加每一帧的发送间隔,确保显示器能够有充足的时间去处理每一帧。

相信大家都会遇到过应用卡顿,卡顿的原因就是因为两帧的刷新时间间隔大于60帧每秒(约16.67ms),导致用户感觉点击或者滑动时,界面没有及时的响应。

前面提到Core Animation Pipeline是以流水线的形式工作的,在理想的状况下我们希望它能够在1/60s内完成图层树的准备工作并提交给渲染进程,而渲染进程在下一次VSync信号到来的时候提交给GPU进行渲染,并在1/60s内完成渲染,这样就不会产生任何的卡顿。

假设UI可以在后台线程中操作,那么在runloop的结尾准备进行渲染的时候,不同线程提交了不同的渲染信息,于是我们就拥有了更多的绘制事务,这个时候Core Animation Pipeline会不断将信息提交,让GPU进行渲染,由于绘制事件的不同步导致了GPU渲染的不同步,可能在上一帧是需要渲染一个label消失的画面,下一帧却又需要渲染这个label改变了文字,最终导致的是界面的不同步。

参考资料

https://juejin.cn/post/6844903763011076110
https://m.mydrivers.com/newsview/623458.html

上一篇 下一篇

猜你喜欢

热点阅读