SwiftUI的任务修饰符示例代码令人困惑

5 swiftui swift-concurrency

这是苹果开发者文档\xe3\x80\x82中的代码

\n
let url = URL(string: "https://example.com")!\n@State private var message = "Loading..."\n\nvar body: some View {\n    Text(message)\n        .task {\n            do {\n                var receivedLines = [String]()\n                for try await line in url.lines {\n                    receivedLines.append(line)\n                    message = "Received \\(receivedLines.count) lines"\n                }\n            } catch {\n                message = "Failed to load"\n            }\n        }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

为什么不在messageUI 线程中更新,如下代码所示

\n
DispatchQueue.main.async {\n    message = "Received \\(receivedLines.count) lines"\n}\n
Run Code Online (Sandbox Code Playgroud)\n

任务块中的代码是否始终在UI线程中运行?

\n

这是我的测试代码。有时任务似乎没有继承其调用者的参与者上下文。

\n
func wait() async {\n    await Task.sleep(1000000000)\n}\n\nThread.current.name = "Main thread"\nprint("Thread in top-level is \\(Thread.current.name)")\n\nTask {\n    print("Thread in task before wait is \\(Thread.current.name)")\n    if Thread.current.name!.isEmpty {\n        Thread.current.name = "Task thread"\n        print("Change thread name \\(Thread.current.name)")\n    }\n    await wait()\n    print("Thread in task after wait is \\(Thread.current.name)")\n\n}\n\nThread.sleep(until: .now + 2)\n\n// print as follow\n\n// Thread in top-level is Optional("Main thread")\n// Thread in task before wait is Optional("")\n// Change thread name Optional("Task thread")\n// Thread in task after wait is Optional("")\n
Run Code Online (Sandbox Code Playgroud)\n

任务中的线程前后不同wait()

\n

任务中的线程不是Main thread

\n

rob*_*off 6

好问题!看起来像是一个bug,但实际上Apple的示例代码是安全的。但出于某种偷偷摸摸的原因,它是一个保险箱。

打开终端窗口并运行以下命令:

cd /Applications/Xcode.app
find . -path */iPhoneOS.platform/*/SwiftUI.swiftmodule/arm64.swiftinterface
Run Code Online (Sandbox Code Playgroud)

find命令可能需要一段时间才能完成,但它最终会打印如下路径:

./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64.swiftinterface
Run Code Online (Sandbox Code Playgroud)

使用 查看该swiftinterface文件并less搜索func task. 您将找到修饰符的真正task定义。我将在这里复制它并换行以使其更易于阅读:

  @inlinable
  public func task(
    priority: _Concurrency.TaskPriority = .userInitiated,
    @_inheritActorContext
    _ action: @escaping @Sendable () async -> Swift.Void
  ) -> some SwiftUI.View {
    modifier(_TaskModifier(priority: priority, action: action))
  }
Run Code Online (Sandbox Code Playgroud)

请注意,action参数具有@_inheritActorContext属性。这是一个私有属性,但Underscored Attributes ReferenceSwift 存储库中的 解释了它的作用:

标记@Sendable async闭包参数应该基于闭包的声明站点继承参与者上下文(即它应该在哪个参与者上运行)。这与典型的行为不同,在典型的行为中,闭包可以在任何地方运行,除非其类型明确声明它将在特定的参与者上运行。

因此task修饰符的action闭包继承了围绕修饰符使用的参与者上下文task。示例代码taskbodya 的属性内使用修饰符Viewbody您还可以在该文件中找到属性的真实声明swiftinterface

  @SwiftUI.ViewBuilder @_Concurrency.MainActor(unsafe) var body: Self.Body { get }
Run Code Online (Sandbox Code Playgroud)

body方法具有MainActor属性,这意味着它属于上下文MainActorMainActor在主线程/队列上运行。因此,使用taskinsidebody意味着task闭包也在主线程/队列上运行。