问题复现
在Xcode预览中,发现点击“统计”模块后,发生报错。

具体的报错提示为:
Can only invoke this method on the main queue
报错翻译为:只能在主队列上调用此方法。
这意味着,我在的代码在非主线程(例如backgroundContext)中调用了只能在主线程中使用的 API。
因为我的统计数据量非常大,频繁调用Core Data查询,因此需要大概50秒的时间,所以我把统计放到backgroundContext中执行。
排查定位
怀疑是backgroundContext导致的问题,通过排查定位发现,当代码中调用appStorage这个管理对象时,因为appStorage是SwiftUI 的 @EnvironmentObject传递的,属于UI环境下的数据,只能在主线程读取,在后台线程访问就会崩溃。
// 轮训汇率的所有日期
private func CalculatingHistoricalHighs() {
backgroundContext.perform {
CalculateForeignCurrencyAmounts(date: rateDate,userCurrencies: UserCurrency)
}
}
func CalculateForeignCurrencyAmounts(date: Date, userCurrencies: [UserForeignCurrency]) {
do {
// appStorage是外部定义的管理对象,只能在主现场读取
if calculateCount >= appStorage.historicalHigh && calculateCount > 0 {
appStorage.historicalHigh = calculateCount
appStorage.historicalTime = date.timeIntervalSince1970
}
} catch {
print("backgroundContext发生了报错")
}
}
解决方案
当需要在主线程操作时,需要在DispatchQueue.main.async中调用:
DispatchQueue.main.async {
if calculateCount >= appStorage.historicalHigh && calculateCount > 0 {
appStorage.historicalHigh = calculateCount
appStorage.historicalTime = date.timeIntervalSince1970
}
}

预览恢复正常。
总结
当代码在非主线程中(例如backgroundContext)中调用时,涉及@EnvironmentObject等SwiftUI代码时,都需要在主线程中调用。
解决方法就是使用DispatchQueue.main.async代码块包裹需要主线程调用的代码。
最后,如果预览仍然报错,需要使用 “Clean Build Folder” 功能,清理构建文件夹,重新构建预览代码。
相关文章
1、Core Data NSManagedObjectContext的后台上下文backgroundContext:https://fangjunyu.com/2025/03/31/core-data-nsmanagedobjectcontext%e7%9a%84%e5%90%8e%e5%8f%b0%e4%b8%8a%e4%b8%8b%e6%96%87backgroundcontext/
2、Swift主线程:https://fangjunyu.com/2024/12/25/swift%e4%b8%bb%e7%ba%bf%e7%a8%8b/
3、Swift中的线程模型:https://fangjunyu.com/2024/12/25/swift%e4%b8%ad%e7%9a%84%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b/