如何在Swift Task/await中调用willChangeValue规避警告?
解决Swift Task/await中调用willChangeValue的警告问题
问题原因
willChangeValue(forKey:) 和 didChangeValue(forKey:) 要求在同步上下文执行,异步Task中存在挂起点(await),跨挂起点触发KVO通知会产生未定义行为,因此Swift 6将这类操作转为错误。
解决方案
1. 切换到主线程同步执行变更与通知
UI相关的属性变更和KVO通知必须在主线程执行,同时保证操作处于无挂起的同步上下文,可通过两种方式实现:
方式一:使用@MainActor限定代码块
在Task内嵌套@MainActor子Task,确保变更操作在主线程同步完成:
Task { await initialise() self.isInitialised = true let local = await self.getLocalFiles() // ... 日志输出代码 // 切换到主线程执行KVO通知和数据更新 Task { @MainActor in self.willChangeValue(forKey: "localFilesTitle") self.localFiles.append(contentsOf: local.filter({!$0.isDirectory})) self.didChangeValue(forKey: "localFilesTitle") } }
方式二:使用DispatchQueue.main.async
直接将变更代码派发到主线程同步执行:
Task { await initialise() self.isInitialised = true let local = await self.getLocalFiles() // ... 日志输出代码 DispatchQueue.main.async { self.willChangeValue(forKey: "localFilesTitle") self.localFiles.append(contentsOf: local.filter({!$0.isDirectory})) self.didChangeValue(forKey: "localFilesTitle") } }
2. 用@MainActor标记属性简化操作
如果localFiles是UI相关数据源,直接用@MainActor修饰它,确保所有对它的修改自动在主线程同步上下文执行:
@MainActor var localFiles: [YourFileType] = [] @objc dynamic var localFilesTitle: String { get { return "\(localTitle)(\(localFiles.count))" } set {} }
此时在Task中更新localFiles时,会自动切换到主线程,只需用MainActor.run包裹变更逻辑即可:
Task { await initialise() self.isInitialised = true let local = await self.getLocalFiles() // ... 日志输出代码 await MainActor.run { self.willChangeValue(forKey: "localFilesTitle") self.localFiles.append(contentsOf: local.filter({!$0.isDirectory})) self.didChangeValue(forKey: "localFilesTitle") } }
额外建议
对于依赖其他属性的计算属性(比如localFilesTitle依赖localFiles.count),可以将localFiles标记为@objc dynamic,通过监听localFiles的变化自动触发localFilesTitle的变更通知,避免手动调用willChange/didChangeValue,代码更简洁可靠。
内容的提问来源于stack exchange,提问作者Duncan Groenewald
相关产品推荐
相关产品推荐

