SwiftUI的task修饰符示例代码比较混乱
The example code of task modifier of SwiftUI is confusing
这里是苹果开发者文档中的代码。
let url = URL(string: "https://example.com")!
@State private var message = "Loading..."
var body: some View {
Text(message)
.task {
do {
var receivedLines = [String]()
for try await line in url.lines {
receivedLines.append(line)
message = "Received \(receivedLines.count) lines"
}
} catch {
message = "Failed to load"
}
}
}
为什么它不更新 UI 线程中的 message
,如下面的代码
DispatchQueue.main.async {
message = "Received \(receivedLines.count) lines"
}
任务块中的代码是否总是运行在UI线程中?
这是我的测试代码。有时任务似乎没有继承其调用者的参与者上下文。
func wait() async {
await Task.sleep(1000000000)
}
Thread.current.name = "Main thread"
print("Thread in top-level is \(Thread.current.name)")
Task {
print("Thread in task before wait is \(Thread.current.name)")
if Thread.current.name!.isEmpty {
Thread.current.name = "Task thread"
print("Change thread name \(Thread.current.name)")
}
await wait()
print("Thread in task after wait is \(Thread.current.name)")
}
Thread.sleep(until: .now + 2)
// print as follow
// Thread in top-level is Optional("Main thread")
// Thread in task before wait is Optional("")
// Change thread name Optional("Task thread")
// Thread in task after wait is Optional("")
任务中的线程前后不同wait()
任务中的线程不是Main thread
好问题!看起来是个bug,但实际上苹果的示例代码是安全的。但出于偷偷摸摸的原因,它是安全的。
打开一个终端window和运行这个:
cd /Applications/Xcode.app
find . -path */iPhoneOS.platform/*/SwiftUI.swiftmodule/arm64.swiftinterface
find
命令可能需要一段时间才能完成,但它最终会打印如下路径:
./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64.swiftinterface
查看带有 less
的 swiftinterface
文件并搜索 func task
。您会发现 task
修饰符的 true 定义。我将在此处重现并换行以使其更易于阅读:
@inlinable
public func task(
priority: _Concurrency.TaskPriority = .userInitiated,
@_inheritActorContext
_ action: @escaping @Sendable () async -> Swift.Void
) -> some SwiftUI.View {
modifier(_TaskModifier(priority: priority, action: action))
}
请注意,action
参数具有 @_inheritActorContext
属性。这是一个私有属性,但是 Swift 存储库中的 Underscored Attributes Reference
解释了它的作用:
Marks that a @Sendable async
closure argument should inherit the actor context (i.e. what actor it should be run on) based on the declaration site of the closure. This is different from the typical behavior, where the closure may be runnable anywhere unless its type specifically declares that it will run on a specific actor.
因此 task
修饰符的 action
闭包继承了围绕 task
修饰符使用的参与者上下文。示例代码在 View
的 body
属性 内使用了 task
修饰符。您还可以在 swiftinterface
文件中找到 body
属性 的真实声明:
@SwiftUI.ViewBuilder @_Concurrency.MainActor(unsafe) var body: Self.Body { get }
body
方法有MainActor
属性,这意味着它属于MainActor
上下文。 MainActor
运行 主要 thread/queue。所以在 body
中使用 task
意味着 task
闭包也在主 thread/queue.
上 运行s
这里是苹果开发者文档中的代码。
let url = URL(string: "https://example.com")!
@State private var message = "Loading..."
var body: some View {
Text(message)
.task {
do {
var receivedLines = [String]()
for try await line in url.lines {
receivedLines.append(line)
message = "Received \(receivedLines.count) lines"
}
} catch {
message = "Failed to load"
}
}
}
为什么它不更新 UI 线程中的 message
,如下面的代码
DispatchQueue.main.async {
message = "Received \(receivedLines.count) lines"
}
任务块中的代码是否总是运行在UI线程中?
这是我的测试代码。有时任务似乎没有继承其调用者的参与者上下文。
func wait() async {
await Task.sleep(1000000000)
}
Thread.current.name = "Main thread"
print("Thread in top-level is \(Thread.current.name)")
Task {
print("Thread in task before wait is \(Thread.current.name)")
if Thread.current.name!.isEmpty {
Thread.current.name = "Task thread"
print("Change thread name \(Thread.current.name)")
}
await wait()
print("Thread in task after wait is \(Thread.current.name)")
}
Thread.sleep(until: .now + 2)
// print as follow
// Thread in top-level is Optional("Main thread")
// Thread in task before wait is Optional("")
// Change thread name Optional("Task thread")
// Thread in task after wait is Optional("")
任务中的线程前后不同wait()
任务中的线程不是Main thread
好问题!看起来是个bug,但实际上苹果的示例代码是安全的。但出于偷偷摸摸的原因,它是安全的。
打开一个终端window和运行这个:
cd /Applications/Xcode.app
find . -path */iPhoneOS.platform/*/SwiftUI.swiftmodule/arm64.swiftinterface
find
命令可能需要一段时间才能完成,但它最终会打印如下路径:
./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64.swiftinterface
查看带有 less
的 swiftinterface
文件并搜索 func task
。您会发现 task
修饰符的 true 定义。我将在此处重现并换行以使其更易于阅读:
@inlinable
public func task(
priority: _Concurrency.TaskPriority = .userInitiated,
@_inheritActorContext
_ action: @escaping @Sendable () async -> Swift.Void
) -> some SwiftUI.View {
modifier(_TaskModifier(priority: priority, action: action))
}
请注意,action
参数具有 @_inheritActorContext
属性。这是一个私有属性,但是 Swift 存储库中的 Underscored Attributes Reference
解释了它的作用:
Marks that a
@Sendable async
closure argument should inherit the actor context (i.e. what actor it should be run on) based on the declaration site of the closure. This is different from the typical behavior, where the closure may be runnable anywhere unless its type specifically declares that it will run on a specific actor.
因此 task
修饰符的 action
闭包继承了围绕 task
修饰符使用的参与者上下文。示例代码在 View
的 body
属性 内使用了 task
修饰符。您还可以在 swiftinterface
文件中找到 body
属性 的真实声明:
@SwiftUI.ViewBuilder @_Concurrency.MainActor(unsafe) var body: Self.Body { get }
body
方法有MainActor
属性,这意味着它属于MainActor
上下文。 MainActor
运行 主要 thread/queue。所以在 body
中使用 task
意味着 task
闭包也在主 thread/queue.