为什么必须在主线程操作UI

探讨了在iOS开发中,为何必须在主线程更新UI的原因。主要从三个方面解析:UIApplication建立于主线程,图形处理流程同步进行,及简化并发编程避免bug。深入理解有助于提升iOS应用性能。

系统: Mac OS 10.14.6, XCode 11,swift 4.2
写作时间:2019-10-12

说明

写iOS的同学都知道,如果更新update UI不在主线程,直接crash。那到底为什么,update UI必须要在主线程呢?原因有如下3点:

1. 在Cocoa Touch, UIApplication建立在主线程,这个单例是App启动的第一部分。

所有在屏幕上面的Views,Label,Button都是建立在UIApplication的实体上。所以当你点击Button,你必须在主线程上处理。同样的,相关操作在UI的事件,比如放大、缩小,动画等都必须要在主线程上处理。

2. 图形的处理流程graphics pipeline,绝对是同步处理的.

举个例子,在UILabel显示用户名. 你异步请求用户的名字,当结果返回时,显示用户名.

UIKit, 需要把字体打印到屏幕上,因为字体本身是向量,需要把字体转换为像素,可能要裁剪字体大小,可能要处理透明度,图形渲染器需要计算哪些像素要显示. 这些像素需要把这些图层放在一起,一秒内处理60次,这样子才不会掉帧.

把像素画到Phone屏幕上,在LED上照亮像素点,这些操作需要一并处理. 这里要是异步处理,就会导致不知道那些哪里已经渲染完,哪里没有. 如果你非要异步处理,会出现屏幕闪烁,有的部分显示不出来的问题。

3. 最后,在主线程上更新UI,是最简单的,省了做各种决定如何显示,避免出现bugs。最佳实践为,异步组装数据提高体验,在主线程同步处理UI。

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.
Thread-Safe Class Design

  1. View磨皮filters过滤器效果,圆角View, 或者用到CIFilter. 这些操作比较集中,需要cache缓存, 这些操作需要异步处理好,最后得到像素数据再调用主线程渲染到屏幕上.
  2. 创建一个复杂动画图片。需要先处理动画数据,最后再生成动画的图片,渲染到屏幕上.
  3. 下载网络资源,比如用户数据,先异步后台下载数据,下载完毕后再调用主线程渲染到屏幕上.

参考

https://www.quora.com/Why-must-the-UI-always-be-updated-on-Main-Thread

https://developer.apple.com/videos/play/wwdc2012/211/

https://juejin.im/post/5c406d97e51d4552475fe178

https://www.objc.io/issues/2-concurrency/thread-safe-class-design/

<think>嗯,用户问为什么一定要在主线程刷新UI。这个问题看起来基础,但背后的原理可能需要详细解释。首先,我需要回忆一下iOS开发中UI操作的相关知识。主线程通常被称为UI线程,所有UI更新都必须在这里进行,否则可能会导致不可预知的问题,比如界面卡顿、崩溃或者显示不正确。 首先,用户可能是刚接触iOS开发的新手,对多线程和UI更新的关系不太清楚。他们可能遇到过在后台线程更新UI导致应用崩溃的情况,想知道背后的原因。也有可能他们在调试时遇到了UI更新的问题,想了解如何正确解决。 接下来,我需要确定回答的结构。应该从主线程的角色开始,解释为什么UIKit不是线程安全的。然后,说明在非主线程刷新UI可能引发的问题,比如竞争条件、UI不同步等。同时,可以提到系统如何检测到非主线程UI操作,比如Xcode的断言和崩溃日志。此外,可能还需要讨论一些例外情况,比如某些系统API可能在后台线程回调,但依然需要在主线程处理UI更新。 用户可能还关心如何确保代码在主线程执行,比如使用GCD的dispatch_async或者performSelectorOnMainThread方法。这部分可以作为注意事项或最佳实践来补充,帮助用户避免常见错误。 另外,需要强调虽然大部分UI操作必须在主线程,但某些计算或数据处理可以在后台线程进行,以优化性能。这有助于用户理解多线程的合理使用场景。 可能还需要提到iOS的RunLoop机制,主线程的RunLoop负责处理UI事件、定时器等,确保UI的流畅响应。如果在其他线程操作UI,由于没有正确的RunLoop处理,可能导致UI更新无法及时执行或出现异常。 最后,总结一下主线程刷新UI的重要性,并提醒用户始终遵循这一原则,以确保应用的稳定性和性能。</think>在 iOS/macOS 开发中,**必须主线程刷新 UI** 的核心原因与 UIKit/AppKit 的设计机制密切相关。以下是完整的解释: --- ### **根本原因** 1. **UIKit/AppKit 的线程不安全** Apple 的 UI 框架(UIKit 用于 iOS,AppKit 用于 macOS)在设计上**并非线程安全**。所有与 UI 相关的操作(创建、修改、布局、渲染)都依赖于主线程的 **RunLoop** 事件循环,若多线程同时操作 UI 会导致**数据竞争**(Race Condition),引发不可预知的崩溃或显示异常。 2. **渲染管线的同步性** - 屏幕渲染需要遵循 **VSync**(垂直同步信号)机制,主线程的 **Core Animation** 会将 UI 变更打包提交给渲染服务(Render Server)。 - 若在其他线程修改 UI,可能导致渲染管线状态不一致(例如:界面元素的位置、尺寸、层级关系冲突)。 --- ### **非主线程操作 UI 的风险** 1. **直接崩溃** Xcode 的调试环境下,UIKit 会通过 **断言(Assertion)** 检测非主线程UI 操作,抛出类似以下错误: `Main Thread Checker: UI API called on a background thread` 在 Release 模式下可能表现为随机崩溃。 2. **UI 状态不一致** 多线程同时修改同一控件属性(如 `text`、`frame`)时,可能因执行顺序不确定导致界面显示错乱。 3. **性能恶化** 非主线程UI 操作会干扰主线程的 RunLoop 调度,可能引发掉帧、卡顿等问题。 --- ### **技术细节** 1. **RunLoop 的职责** 主线程的 **RunLoop** 负责处理所有 UI 事件(触摸、动画、布局、绘制)。当你在主线程调用 `setNeedsLayout` 或 `setNeedsDisplay` 时,RunLoop 会在下一个周期统一处理这些 UI 变更请求。 2. **渲染服务的限制** 渲染服务(如 Core Animation)要求所有图层树(Layer Tree)的修改必须在**提交事务(Commit Transaction)** 前完成,而事务提交只能在主线程进行。 --- ### **如何强制主线程刷新?** 通过以下方式确保代码在主线程执行: ```objc // Objective-C dispatch_async(dispatch_get_main_queue(), ^{ // 更新 UI 的代码(如 label.text = @"Hello") }); // 或使用更简洁的宏 #define SAFE_CALL_MAIN(block) \ if ([NSThread isMainThread]) { block(); } \ else { dispatch_async(dispatch_get_main_queue(), block); } // 使用示例 SAFE_CALL_MAIN(^{ [self.tableView reloadData]; }); ``` --- ### **例外情况** 某些系统 API 的回调可能发生在非主线程(如网络请求完成回调),但这些回调中若需要操作 UI,**仍需主动切换回主线程**: ```objc // 示例:NSURLSession 的回调在后台队列 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { dispatch_async(dispatch_get_main_queue(), ^{ self.progressView.hidden = YES; }); } ``` --- ### **调试与检测** 1. **Xcode 主线程检查器** 在 Scheme 设置中启用 `Main Thread Checker`(默认开启),可自动检测非主线程UI 调用。 2. **手动断言** 在关键 UI 代码前添加断言: ```objc NSAssert([NSThread isMainThread], @"This method must be called on the main thread"); ``` --- ### **总结** - **必须遵守规则**:所有直接或间接操作 UI 的代码(包括修改控件属性、触发布局、调整视图层级等)必须在主线程执行。 - **底层代价**:违反规则可能导致瞬时崩溃、难以调试的 UI 错误,甚至破坏用户体验。 - **性能平衡**:耗时操作(如网络请求、数据解析)应在后台线程完成,但最终 UI 更新仍需回到主线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值