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

查看带有 lessswiftinterface 文件并搜索 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 修饰符使用的参与者上下文。示例代码在 Viewbody 属性 内使用了 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