You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中Task的Actor归属疑问:异步更新UI报错解析

Swift async/await 与 Task 线程问题:ObservableObject 更新UI报错解析

问题场景

在学习Swift的async/await和Task时,了解到Task与Actor的关联特性,但编写代码时触发「从后台线程更新UI」的错误。

简化代码示例

模型代码:

class SomeModel: ObservableObject {
   @Published var downloads: [Int] = []

   func doSome() async throws {
      // 创建URL
      downloads.append(1)

      try await // 发起请求
      downloads.append(data) // 请求完成后
   }
}

视图代码:

struct DownloadView: View {

  @EnvironmentObject var model: SomeModel

  var body: some View {
    List {
      // 使用model.downloads
    }
    .task {
        do {
          try await model.doSome()
        } catch {}
    }
}

运行后触发「从后台线程更新UI」错误,即使将await后的更新放在MainActor.run中,仍会报错。

疑问点

  • 系统无法识别第一个append始终在Main Actor上执行?
  • 这是防止后续添加挂起代码的安全机制?
  • 已知层级await会让系统知晓挂起的子任务树,但第一个append后才是挂起点,理论上不会切换到其他Actor,为何仍报错?

2024年10月31日补充:已理解Swift遇到await时,后续代码可能在调用线程之外的其他线程执行(类似Dispatch.current.async,Task会在主线程完成后切换到其他线程启动),但上述疑问仍未解决。

完整复现代码

DownloadFile 模型

struct DownloadFile: Identifiable {
    var id: String { return name }
    let name: String
    let size: Int
    let date: Date
    
    static let mockFiles = [DownloadFile(name: "File1", size: 100, date: Date()),
                            DownloadFile(name: "File2", size: 100, date: Date()),
                            DownloadFile(name: "File3", size: 100, date: Date()),
                            DownloadFile(name: "File4", size: 100, date: Date()),
                            DownloadFile(name: "File5", size: 100, date: Date()),
                            DownloadFile(name: "FIle6", size: 100, date: Date())]
    
    static let empty = DownloadFile(name: "", size: 0, date: Date())
}

ContentView

struct ContentView: View {
    @State var files: [DownloadFile] = []
    let model: ViewModel
    
    @State var selected = DownloadFile.empty {
      didSet {
        isDisplayingDownload = true
      }
    }
    
    @State var isDisplayingDownload = false
    
    var body: some View {
      NavigationStack {
        VStack {
          // 可下载文件列表
          List {
            Section(content: {
              if files.isEmpty {
                ProgressView().padding()
              }
              ForEach(files) { file in
                Button(action: {
                  selected = file
                }, label: {
                    Text(file.name)
                })
              }
            })
          }
          .listStyle(.insetGrouped)
        }
        .task {
            try? await Task.sleep(for: .seconds(1))
            files = DownloadFile.mockFiles
        }
        .navigationDestination(isPresented: $isDisplayingDownload) {
          DownloadView(file: selected).environmentObject(model)
        }
      }
    }
}

DownloadView

struct DownloadView: View {
    let file: DownloadFile
    @EnvironmentObject var model: ViewModel
    @State var result: String = ""
    var body: some View {
        VStack {
            Text(file.name)
            if model.downloads.isEmpty {
                Button {
                    Task {
                        result = try await model.download(file: file)
                    }
                } label: {
                    Text("-- Download --")
                }
            }
            Text(result)
        }
    }
}

ViewModel

class ViewModel: ObservableObject {
    @Published var downloads: [String] = []
    
    func download(file: DownloadFile) async throws -> String {
        downloads.append(file.name)

        try await Task.sleep(for: .seconds(2))
        return "Download Finished"
    }
}

内容的提问来源于stack exchange,提问作者Dragonboh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 02:00:23