在 Android 开发中我们所有 UI 操作只允许主线程和主队列操作(一些检查没开启不做讨论). 在学习iOS我一直也这样认为.
直到我看到下面的例子:
swift
import SwiftUI
func helloFun() async{
print("hello 3 ")
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
}
.padding().task {
print("hello 1")
await Task.detached {
print("hello 2")
await helloFun()
}
}
}
}
#Preview {
ContentView()
}
正常情况预期情况如下:
位置 | 线程 | 队列 |
---|---|---|
hello1 | 主线程 | 主队列 |
hello2 | 子线程 | 全局队列 |
hello3 | 主线程 | 主队列 |
我将断点hello1,hello2,hello3 时使用 lldb 查看线程信息:
arduino
(lldb) thread list
Process 4136 stopped
* thread #8: tid = 0xf00000002, 0x00000001049422c8 LearnConcurrency.debug.dylib`closure #2 in ContentView.body.getter() at ContentView.swift:29:19, name = 'Task 2', queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
"hello 1" 0x0000000104a1e354
(lldb) thread list
Process 4136 stopped
* thread #13: tid = 0xf00000003, 0x0000000104a1e51c LearnConcurrency.debug.dylib`closure #1 in closure #2 in ContentView.body.getter() at ContentView.swift:31:22, name = 'Task 3', queue = 'com.apple.root.default-qos.cooperative', stop reason = breakpoint 3.1
"hello 2" 0x0000000104a1e51c
(lldb) thread list
Process 4136 stopped
* thread #13: tid = 0xf00000003, 0x0000000104a1d6ac LearnConcurrency.debug.dylib`helloFun() at ContentView.swift:14:11, name = 'Task 3', queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
"hello 3" 代码位于 0x104a1d6ac
输出总结:
位置 | tid | 队列 |
---|---|---|
hello1 | 0xf00000002 | com.apple.main-thread |
hello2 | 0xf00000003 | com.apple.root.default-qos.cooperative |
hello3 | 0xf00000003 | com.apple.main-thread |
初学 iOS 的小白愣了一会.反复查看 hello 1 汇编代码 PC 寄存地址确认没有错误.
hello1 和 hello3 都可以视为在主线程执行,因为加入类似的如下代码会卡死界面:
swift
func helloFun() async{
print("hello 3 ")
//会卡死界面
while true{
}
}
我一直以为主线程和主队列是 1 对 1 的关系,但是看到这个信息感觉是所有主队列任务可运行在任何线程.但是主队列任务是串行执行.
hello1 和 hello3 的 tid 截然不同的,这和 Android 成为了巨大反差. 但是在 hello2添加类似的代码不会.
swift
task {
print("hello 1")
await Task.detached {
print("hello 2")
//不会卡死 UI
while true{
}
await helloFun()
}
}
所以粗浅的得到下面结论:
- func helloFun()会默认继承 MainActor,所以定会在主队列执行.
- MainActor不保证在主线程执行,但是保证在主队列.
- 为了屏蔽线程差异,swift 在所有 async 函数中全部屏蔽Thread.current调用.
- UI 元素不一定要在主线程,但是一定要在主队列
- 主队列执行可能在子线程,但是会阻塞后续队列执行直到完成以避免主线程的竞争问题
- Thread.current.isMainThread 可能被底层封装了.即便当前是一个个其他线程.或者他以主队列作为主线程标准
一些技巧
-
虽然swift6 不允许在 async 编写 Thread.current 但是可以在 LLDB 中输入 po Thread.current去调试查看
-
使用 lldb 的 thread list可以查看所有线程的信息.
-
使用 lldb 的 thread info 可以查看当前线程