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
相关产品推荐
相关产品推荐

