1️⃣ 核心结论
在 MainActor 上执行耗时任务会阻塞主线程,导致 UI 卡顿、动画停滞和响应延迟。
优化方法是:将耗时任务移到后台线程执行(非 MainActor 上下文),完成后再切换回 MainActor 更新 UI。
2️⃣ 为什么会卡
MainActor 执行原理
- MainActor executor 固定绑定 主线程
- 同一时间 只能有一个任务执行
- 所有 @MainActor 方法和属性访问都会排队执行
卡顿示例
@MainActor
func loadData() {
// 耗时操作
for i in 0..<1_000_000 { _ = i*i }
label.text = "Done" // UI 更新
}
问题:
for循环在主线程上执行 → 阻塞 executor- UI 无法刷新 → 应用卡死
- 异步任务、动画和用户交互都被阻塞
3️⃣ 优化策略
① 移动耗时任务到后台
使用 非 MainActor context:
Task.detached {
let data = loadDataFromNetwork() // 耗时操作
await MainActor.run {
label.text = data
}
}
Task.detached→ 在后台线程执行await MainActor.run { ... }→ 回到主线程更新 UI- UI 不阻塞,响应流畅
② 使用 async/await + actor 分隔
actor DataLoader {
func fetchData() async -> String {
// 耗时操作,可以并发
return await someNetworkCall()
}
}
@MainActor
func updateUI() async {
let loader = DataLoader()
let data = await loader.fetchData() // 不阻塞主线程
label.text = data
}
- Actor 内部可并发执行耗时任务
- MainActor 只负责 UI 更新
- 保持主线程流畅
③ 小技巧
- 不要在 @MainActor async 函数里做 CPU 密集计算
- CPU 密集计算 → Task.detached / background queue
- I/O 或网络 → async/await 本身不会阻塞线程,但如果在 MainActor 包裹同步阻塞操作(如 FileManager.read)仍会卡
4️⃣ 类比理解
- MainActor = “主线程队列 + 串行执行任务”
- 耗时任务 = “一个大箱子阻塞队列”
- 后果 = 所有 UI 和动画任务必须等待 → 卡顿
- 解决方法 = “把大箱子搬到后台仓库,搬完再回到主线程更新”
5️⃣ 面试必背总结
- 在 MainActor 上执行耗时任务会阻塞主线程,导致 UI 卡顿、动画停滞和响应延迟。
- 优化方式:将耗时计算或 I/O 移到后台线程(Task.detached / 非 MainActor actor),完成后再
await MainActor.run回到主线程更新 UI。- 原理:MainActor executor 串行化任务,任何耗时操作都会阻塞 executor 的队列。