我有一个仅使用本地设备 CoreData (NSPersistentContainer) 的应用程序。我正在寻求迁移,因此该应用程序与 NSPersistentCloudKitContainer 兼容。我了解 NSPersistentCloudKitContainer 的所有 CloudKit 设置,但是如何将玩家手机上的数据迁移到 iCloud?(即如何将现有核心数据从 NSPersistentContainer 迁移到 NSPersistentCloudKitContainer)?
2 回答
我做了以下事情:
- 替换
NSPersistentContainer
为NSPersistentCloudKitContainer
- 并添加了启用的历史跟踪
container = NSPersistentCloudKitContainer(name: "myApp") // <<<<< this
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey) // <<<<< this
container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores(...)
编辑:嗯,我说得太快了。这没用 :(
我发现人们在这里有一些小技巧https://developer.apple.com/forums/thread/120328 (例如编辑项目以触发同步,或手动传输每个对象,如https://medium 中所述。 com/@dmitrydeplov/coredata-cloudkit-integration-for-a-live-app-57b6cfda84ad)
但是没有真正的答案...
2019 年 WWDC 视频“<a href="https://developer.apple.com/videos/play/wwdc2019/202/" rel="nofollow noreferrer">Using Core Data With云套件“。
要点是:
- 替换
NSPersistentContainer
为 ist 子类NSPersistentClouKitContainer
。 - 使用容器函数初始化 iCloud 模式
initializeCloudKitSchema
(这只需在核心数据模型设置或更改后执行一次)。 - 在 iCloud 仪表板中,使每个自定义类型(这些类型以 CD_ 开头)都可查询。
- 在 iCloud Dashboard 中,为所有经过身份验证的用户设置所有 CD_ 记录类型的安全类型为读/写。
- 实现核心数据的历史跟踪(这里是 Apple 的建议)。
如果您有多个持久存储(例如,仅与一台设备相关的本地存储、与具有相同 Apple ID 的所有用户共享的私有存储以及与其他用户共享的共享存储),一种设置方法如下:
private (set) lazy var persistentContainer: NSPersistentCloudKitContainer! = {
// This app uses 3 stores:
// - A local store that is user-specific,
// - a private store that is synchronized with the iCloud private database, and
// - a shared store that is synchronized with the iCloud shared database.
let persistentStoresLoadedLock = DispatchGroup.init() // Used to wait for loading the persistent stores
// Configure local store
// --------------------------------------------------------------------------------------------------
let appDocumentsDirectory = try! FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
let coreDataLocalURL = appDocumentsDirectory.appendingPathComponent("CoreDataLocal.sqlite")
let localStoreDescription = NSPersistentStoreDescription(url: coreDataLocalURL)
localStoreDescription.configuration = localConfigurationName
// --------------------------------------------------------------------------------------------------
// Create a container that can load the private store as well as CloudKit-backed stores.
let container = NSPersistentCloudKitContainer(name: appName)
assert(container.persistentStoreDescriptions.count == 1, "###\(#function): Failed to retrieve a persistent store description.")
let firstPersistentStoreDescription = container.persistentStoreDescriptions.first!
let storeURL = firstPersistentStoreDescription.url!
let storeURLwithoutLastPathComponent = storeURL.deletingLastPathComponent
// Configure private store
// --------------------------------------------------------------------------------------------------
let privateStoreDescription = firstPersistentStoreDescription
privateStoreDescription.configuration = privateConfigurationName
// The options below have to be set before loadPersistentStores
// Enable history tracking and remote notifications
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
privateStoreDescription.cloudKitContainerOptions!.databaseScope = .private
// --------------------------------------------------------------------------------------------------
// Configure shared store
// --------------------------------------------------------------------------------------------------
let sharedStoreURL = storeURLwithoutLastPathComponent().appendingPathComponent("Shared")
let sharedStoreDescription = NSPersistentStoreDescription(url: sharedStoreURL)
sharedStoreDescription.configuration = sharedConfigurationName
sharedStoreDescription.timeout = firstPersistentStoreDescription.timeout
sharedStoreDescription.type = firstPersistentStoreDescription.type
sharedStoreDescription.isReadOnly = firstPersistentStoreDescription.isReadOnly
sharedStoreDescription.shouldAddStoreAsynchronously = firstPersistentStoreDescription.shouldAddStoreAsynchronously
sharedStoreDescription.shouldInferMappingModelAutomatically = firstPersistentStoreDescription.shouldInferMappingModelAutomatically
sharedStoreDescription.shouldMigrateStoreAutomatically = firstPersistentStoreDescription.shouldMigrateStoreAutomatically
// The options below have to be set before loadPersistentStores
// Enable history tracking and remote notifications
sharedStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
sharedStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
sharedStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions.init(containerIdentifier: "iCloud.com.zeh4soft.shop")
// For sharing see https://developer.apple.com/documentation/cloudkit/shared_records
// and https://medium.com/@adammillers/cksharing-step-by-step-33800c8950d2
sharedStoreDescription.cloudKitContainerOptions!.databaseScope = .shared
// --------------------------------------------------------------------------------------------------
container.persistentStoreDescriptions = [localStoreDescription, privateStoreDescription, sharedStoreDescription]
for _ in 1 ... container.persistentStoreDescriptions.count { persistentStoresLoadedLock.enter() }
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
// The completion handler will be called once for each persistent store that is created.
guard error == nil else {
/*
Apple suggests to replace this implementation with code to handle the error appropriately.
However, there is not really an option to handle it, see <https://stackoverflow.com/a/45801384/1987726>.
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("###\(#function): Failed to load persistent stores: \(error!)")
}
if storeDescription.configuration == self.privateConfigurationName {
/*
Only if the schema has been changed, it has to be re-initialized.
Due to an Apple bug, this can currently (iOS 13) only be done with a .private database!
A re-initialization requires to run the app once using the scheme with the "-initSchema" argument.
After schema init, ensure in the Dashboard:
For every custom type, recordID and modTime must have queryable indexes.
All CD record types must have read/write security type for authenticated users.
Run later always a scheme without the "-initSchema" argument.
*/
if ProcessInfo.processInfo.arguments.contains("-initSchema") {
do {
try container.initializeCloudKitSchema(options: .printSchema)
} catch {
print("-------------------- Could not initialize cloud kit schema --------------------")
}
}
}
persistentStoresLoadedLock.leave() // Called for all stores
})
let waitResult = persistentStoresLoadedLock.wait(timeout: .now() + 100) // Wait for local, private and shared stores loaded
if waitResult != .success { fatalError("Timeout while loading persistent stores") }
return container
} ()
编辑:
privateConfigurationName
sharedConfigurationName
,还有Strings
:
let privateConfigurationName = "Private"
let sharedConfigurationName = "Shared"
和Private
和Shared
用作 Coredata 模型中的配置名称,例如:
必须将实体分配给持久存储。
警告:
如果您将同一个实体分配给多个持久存储,则托管上下文的保存会将其存储在所有分配的存储中,除非您分配特定的存储,请参阅这篇文章。
同样,提取将从实体分配到的所有持久存储中提取记录,除非您affectedStores
在提取请求中设置,请参阅文档。