注意问题
请尝试Command+Q强退Xcode项目,然后重新打开。如果问题仍然存在,请查看下面的教程。
问题描述
在Xcode中,发现定义的单例shared报错:
class IAPManager:ObservableObject {
static let shared = IAPManager() // 报错行
@Published var products: [Product] = [] // 存储从 App Store 获取的内购商品信息
...
}
报错代码为:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
报错原因:ObservableObject 发布(更新)其 @Published 属性的变化时发生在后台线程,而 SwiftUI 要求这些更新必须在主线程上进行。
IAPManager 中,有一些操作是异步执行的,例如网络请求或者其他后台任务。如果这些操作修改了 @Published 属性(比如 products 或 purchaseResult),但没有切换到主线程,就会引发这个错误。
解决方法
需要确保在后台线程完成操作后,将结果切换回主线程来更新 @Published 属性。这可以通过 DispatchQueue.main.async 或者 Task { @MainActor in } 来实现。例如:
func loadProduct() async {
do {
print("加载产品中...")
let fetchedProducts = try await fetchProduct()
DispatchQueue.main.async { // 确保主线程执行代码块,这样@Published属性更新不会引起异常
self.products = fetchedProducts // 闭包注意使用self
}
} catch {
print("加载产品失败:\(error)")
}
}
或者使用 @MainActor 来显式地标记代码在主线程上运行:
@MainActor
func updateProducts(_ products: [Product]) {
self.products = products
}
func loadProduct() async {
do {
print("加载产品中...")
let fetchedProducts = try await fetchProduct()
await updateProducts(fetchedProducts)
print("成功加载产品: \(products)")
} catch {
print("加载产品失败:\(error)")
}
}
DispatchQueue.main.async { }:确保在主线程上执行代码块,这样 @Published 属性的更新不会引发异常。
@MainActor:将整个方法或任务标记为主线程执行,简化线程管理。
确保所有 @Published 属性的更新都是在主线程上进行,就可以解决这个问题。
总结
@MainActor
class IAPManager:ObservableObject {...}
我们需要确保类声明的@MainActor,每个更新操作都使用DispatchQueue.main.async。
func handlePurchase(num: Int) {
Task { @MainActor in
DispatchQueue.main.async {
self.savePurchasedState(for: self.products[num].id)
}
...
}
}
此外我们也可以将
DispatchQueue.main.async 的地方改为直接 await MainActor.run,可以确保这些操作都在主线程上(这是个替代方案)。
涉及Task部分使用@MainActor:
func handlePurchase(num: Int) {
Task { @MainActor in
...
}
}
注意:以上操作全部完成之后,强退Xcode重新打开项目,报错消失。