CloudKit + 核心数据:如何仅写入或读取 public 数据库
CloudKit + Core Data: How to write or read the public database only
我在 Public 数据库配置模式和私有数据库配置模式中都有一个记录类型。
当我使用 PersistentStore.shared.context 写入记录类型时,它会将记录写入私有数据库和 public 数据库。当我使用@FetchRequest 查询记录类型时,它 returns 来自 public 和私有数据库的记录。
如何写入或读取 public 或私人数据库?
我的 PersistentStore Stack 基本上是苹果 WWDC 代码的复制粘贴:
class PersistentStore: ObservableObject {
var context: NSManagedObjectContext { persistentContainer.viewContext }
// One line singleton
static let shared = PersistentStore()
private let persistentStoreName: String = "XXXX"
let containerIdentifier: String = "iCloud.com.XXXX.XXXX"
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
//let container = NSPersistentContainer(name: persistentStoreName)
// OR - Include the following line for use with CloudKit - NSPersistentCloudKitContainer
let container = NSPersistentCloudKitContainer(name: persistentStoreName)
// Enable history tracking
// (to facilitate previous NSPersistentCloudKitContainer's to load as NSPersistentContainer's)
// (not required when only using NSPersistentCloudKitContainer)
guard let persistentStoreDescriptions = container.persistentStoreDescriptions.first else {
fatalError("\(#function): Failed to retrieve a persistent store description.")
}
let storesURL = persistentStoreDescriptions.url!.deletingLastPathComponent()
//private database
let privateStoreURL = storesURL.appendingPathComponent("\(persistentStoreName)-private.sqlite")
let privateStoreDescription = NSPersistentStoreDescription(url: privateStoreURL)
privateStoreDescription.configuration = "Private"
privateStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
privateStoreDescription.cloudKitContainerOptions?.databaseScope = .private
//public database
let publicStoreURL = storesURL.appendingPathComponent("\(persistentStoreName)-public.sqlite")
let publicStoreDescription = NSPersistentStoreDescription(url: publicStoreURL)
publicStoreDescription.configuration = "Public"
publicStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
publicStoreDescription.cloudKitContainerOptions?.databaseScope = .public
container.persistentStoreDescriptions = [publicStoreDescription, privateStoreDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
// Replace this implementation with code to handle the error appropriately.
fatalError("Unresolved error \(error)")
}
})
// Include the following line for use with CloudKit - NSPersistentCloudKitContainer
container.viewContext.automaticallyMergesChangesFromParent = true
// Include the following line for use with CloudKit and to set your merge policy, for example...
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// Mark the class private so that it is only accessible through the singleton `shared` static property
private init() {}
// MARK: - Core Data Saving and "other future" support (such as undo)
func save() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Customize this code block to include application-specific recovery steps.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
您可以使用 assign 来选择将新对象保存到哪个存储区
https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext/1506436-assign
对于请求from/to某家商店你可以使用affectedStores
https://developer.apple.com/documentation/coredata/nsfetchrequest/1506518-affectedstores
请注意,您可能更幸运地在模型编辑器中为 public 和私有创建 2 个配置,并为每个配置分配不同的实体。然后自动从实体对应的store中保存和获取。
我有一个类似的要求:我的应用程序使用 CloudKit 和核心数据与私有和共享数据库,并且实体(Item
或 Place
)被分配给两者商店,但一次只能使用其中一个。
为了处理这个问题,我使用了一个 var currentlyUsedStores
设置为私有持久存储或共享持久存储。
新实体在 viewContext
或 backgroundContext
中初始化,但在保存上下文之前,上下文发送通知:
// Register for notifications that the view context will save.
NotificationCenter.default.addObserver(self,
selector: #selector(viewContextOrBackgroundContextWillSave),
name: .NSManagedObjectContextWillSave,
object: viewContext)
// Register for notifications that the background context will save.
NotificationCenter.default.addObserver(self,
selector: #selector(viewContextOrBackgroundContextWillSave),
name: .NSManagedObjectContextWillSave,
object: backgroundContext)
@objc func viewContextOrBackgroundContextWillSave(_ notification: Notification) {
guard let context = notification.object as? NSManagedObjectContext else { return }
let inserts = context.insertedObjects
let itemsAndPlaces = inserts.filter({ [=10=] is Item || [=10=] is Place })
itemsAndPlaces.forEach({ context.assign([=10=], to: currentlyUsedStores.first!) })
}
由于 assign
可以在保存新插入的对象之前使用,所有新对象现在仅存储在当前使用的持久存储中。
要仅从当前使用的商店中获取,必须在获取请求中设置 affectedStores
属性,例如
let itemFetchRequest = NSFetchRequest<Item>(entityName: Item.entityName)
itemFetchRequest.affectedStores = currentlyUsedStores
然后切换当前使用的商店(也可以保存,例如在用户默认中,只需更新var currentlyUsedStores
。
编辑:
NSBatchInsertRequest
也被 NSManagedContext
执行。这样,只要context中新插入的对象,都可以用同样的方式赋值给相关的持久化存储。
我在 Public 数据库配置模式和私有数据库配置模式中都有一个记录类型。
当我使用 PersistentStore.shared.context 写入记录类型时,它会将记录写入私有数据库和 public 数据库。当我使用@FetchRequest 查询记录类型时,它 returns 来自 public 和私有数据库的记录。
如何写入或读取 public 或私人数据库?
我的 PersistentStore Stack 基本上是苹果 WWDC 代码的复制粘贴:
class PersistentStore: ObservableObject {
var context: NSManagedObjectContext { persistentContainer.viewContext }
// One line singleton
static let shared = PersistentStore()
private let persistentStoreName: String = "XXXX"
let containerIdentifier: String = "iCloud.com.XXXX.XXXX"
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
//let container = NSPersistentContainer(name: persistentStoreName)
// OR - Include the following line for use with CloudKit - NSPersistentCloudKitContainer
let container = NSPersistentCloudKitContainer(name: persistentStoreName)
// Enable history tracking
// (to facilitate previous NSPersistentCloudKitContainer's to load as NSPersistentContainer's)
// (not required when only using NSPersistentCloudKitContainer)
guard let persistentStoreDescriptions = container.persistentStoreDescriptions.first else {
fatalError("\(#function): Failed to retrieve a persistent store description.")
}
let storesURL = persistentStoreDescriptions.url!.deletingLastPathComponent()
//private database
let privateStoreURL = storesURL.appendingPathComponent("\(persistentStoreName)-private.sqlite")
let privateStoreDescription = NSPersistentStoreDescription(url: privateStoreURL)
privateStoreDescription.configuration = "Private"
privateStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
privateStoreDescription.cloudKitContainerOptions?.databaseScope = .private
//public database
let publicStoreURL = storesURL.appendingPathComponent("\(persistentStoreName)-public.sqlite")
let publicStoreDescription = NSPersistentStoreDescription(url: publicStoreURL)
publicStoreDescription.configuration = "Public"
publicStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
publicStoreDescription.cloudKitContainerOptions?.databaseScope = .public
container.persistentStoreDescriptions = [publicStoreDescription, privateStoreDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
// Replace this implementation with code to handle the error appropriately.
fatalError("Unresolved error \(error)")
}
})
// Include the following line for use with CloudKit - NSPersistentCloudKitContainer
container.viewContext.automaticallyMergesChangesFromParent = true
// Include the following line for use with CloudKit and to set your merge policy, for example...
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// Mark the class private so that it is only accessible through the singleton `shared` static property
private init() {}
// MARK: - Core Data Saving and "other future" support (such as undo)
func save() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Customize this code block to include application-specific recovery steps.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
您可以使用 assign 来选择将新对象保存到哪个存储区
https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext/1506436-assign
对于请求from/to某家商店你可以使用affectedStores
https://developer.apple.com/documentation/coredata/nsfetchrequest/1506518-affectedstores
请注意,您可能更幸运地在模型编辑器中为 public 和私有创建 2 个配置,并为每个配置分配不同的实体。然后自动从实体对应的store中保存和获取。
我有一个类似的要求:我的应用程序使用 CloudKit 和核心数据与私有和共享数据库,并且实体(Item
或 Place
)被分配给两者商店,但一次只能使用其中一个。
为了处理这个问题,我使用了一个 var currentlyUsedStores
设置为私有持久存储或共享持久存储。
新实体在 viewContext
或 backgroundContext
中初始化,但在保存上下文之前,上下文发送通知:
// Register for notifications that the view context will save.
NotificationCenter.default.addObserver(self,
selector: #selector(viewContextOrBackgroundContextWillSave),
name: .NSManagedObjectContextWillSave,
object: viewContext)
// Register for notifications that the background context will save.
NotificationCenter.default.addObserver(self,
selector: #selector(viewContextOrBackgroundContextWillSave),
name: .NSManagedObjectContextWillSave,
object: backgroundContext)
@objc func viewContextOrBackgroundContextWillSave(_ notification: Notification) {
guard let context = notification.object as? NSManagedObjectContext else { return }
let inserts = context.insertedObjects
let itemsAndPlaces = inserts.filter({ [=10=] is Item || [=10=] is Place })
itemsAndPlaces.forEach({ context.assign([=10=], to: currentlyUsedStores.first!) })
}
由于 assign
可以在保存新插入的对象之前使用,所有新对象现在仅存储在当前使用的持久存储中。
要仅从当前使用的商店中获取,必须在获取请求中设置 affectedStores
属性,例如
let itemFetchRequest = NSFetchRequest<Item>(entityName: Item.entityName)
itemFetchRequest.affectedStores = currentlyUsedStores
然后切换当前使用的商店(也可以保存,例如在用户默认中,只需更新var currentlyUsedStores
。
编辑:
NSBatchInsertRequest
也被 NSManagedContext
执行。这样,只要context中新插入的对象,都可以用同样的方式赋值给相关的持久化存储。