在 SwiftUI 项目的ViewModel文件中获取上下文和数组对象,将数据插入到SwiftData。
private func createLifeSavingBank() throws {
// 获取上下文
let container: try? ModelContainer(for: PiggyBank.self)
let context = container.mainContext
// 创建新的人生存钱罐
let piggyBank = PiggyBank()
context.insert(piggyBank) // 将对象插入到上下文中
// 保存上下文
do {
try context.save() // 提交上下文中的所有更改
} catch {
throw CalculationError.saveFailed(error)
}
}
当 context.save() 调用,并保存到上下文时。UI界面并没有显示创建的存钱罐。
Xcode提示:
CloudKit setup failed because it couldn't register a handler for the export activity. There is another instance of this persistent store actively syncing with CloudKit in this process.
该问题表示,Core Data 在 CloudKit 模式下,每个 NSPersistentCloudKitContainer 会注册一个“导出活动 handler”(export handler),用于把本地改动同步到 CloudKit。
同一个持久化存储(store)已经有一个同步 handler 在运行,又尝试创建了另一个 container 或者上下文去操作同一个 store。
结果:Core Data 内部会报错,但通常它会“重置内部状态”,然后继续尝试同步。
提示:SwiftData 底层为 CoreData,因此这里会出现CoreData相关的内容。
需要等待一段时间,才会看到新创建的存钱罐。这是因为本地已经存在,但是CloudKit同步有问题。
问题原因
经过排查发现,问题的主要原因在于,app启动时已经创建了一次 ModelContainer。
@main
struct pigletApp: App {
@State private var modelConfigManager = ModelConfigManager()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(try! ModelContainer(for: PiggyBank.self,SavingsRecord.self,configurations: modelConfigManager.currentConfiguration))
}
}
在ViewModel文件中,每次调用createLifeSavingBank() 又会创建新的 ModelContainer(for: PiggyBank.self)。
这就导致重复创建ModelContainer,而触发报错。
解决方案
因为ModelContainer需要在View视图以及类中访问,可以创建一个全局单例管理器,只初始化一次ModelContainer:
import SwiftUI
import SwiftData
@MainActor
final class DataController {
static let shared = DataController()
let container: ModelContainer
var context: ModelContext { container.mainContext }
private init() {
self.container = try! ModelContainer(
for: PiggyBank.self,
SavingsRecord.self,
configurations: ModelConfigManager.shared.currentConfiguration)
}
}
在入口文件注入单例:
@main
struct pigletApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(DataController.shared.container) // 注入全局唯一 container
}
}
在View视图中可以通过@Environment(\.modelContext) 获取:
struct ContentView: View {
@Environment(\.modelContext) private var context // 从 Environment 获取 context
@Query(sort: \.creationDate, order: .forward)
private var piggyBanks: [PiggyBank]
...
}
在ViewModel文件中,也可以直接使用ModelContainer单例:
private func createLifeSavingBank() throws {
private let context = DataController.shared.context
// 创建新的人生存钱罐
let piggyBank = PiggyBank()
context.insert(piggyBank) // 将对象插入到上下文中
// 保存上下文
do {
try context.save() // 提交上下文中的所有更改
} catch {
throw CalculationError.saveFailed(error)
}
}
通过全局单例,统一管理ModelContainer容器,这样可以保证 CloudKit handler 只注册一次,避免报错。
扩展内容
完整报错信息:
BUG IN CLIENT OF CLOUDKIT: Registering a handler for a CKScheduler activity identifier that has already been registered (com.apple.coredata.cloudkit.activity.export.1...F).
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](605): <NSCloudKitMirroringDelegate: 0x3019182d0> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134422 "CloudKit setup failed because it couldn't register a handler for the export activity. There is another instance of this persistent store actively syncing with CloudKit in this process." UserInfo={NSUnderlyingException=Illegal attempt to register a second handler for activity identifier com.apple.coredata.cloudkit.activity.export.1...F, NSURL=file:///private/var/mobile/Containers/Shared/AppGroup/7F818A8E-12EE-446C-8862-12911100FE01/Library/Application%20Support/default.store, activityIdentifier=com.apple.coredata.cloudkit.activity.export.134832D0-817B-4347-8C74-D8C97EEB158F, NSLocalizedFailureReason=CloudKit setup failed because it couldn't register a handler for the export activity. There is another instance of this persistent store actively syncing with CloudKit in this process.}
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate tearDown:]_block_invoke(808): <NSCloudKitMirroringDelegate: 0x3019182d0>: Told to tear down with reason: Error NSCocoaErrorDomain:134422
