想做云端数据备份,调研一番后发现只需要在操作数据时用NSPersistentCloudKitContainer替代原本NSPersistentContainer即可
注意事项:
- 需要在设置- iCloud设置中同时开启“iCloud网盘”和自己应用的iCloud访问权限。
- 无需APP得到Wi-Fi或流量的网络授权,也可实现同步
- 关闭iCloud同步,一旦启用了云端备份的
NSPersistentCloudKitContainer,可切换到NSPersistentContainer操作数据,注意设置NSPersistentContainer的NSPersistentHistoryTrackingKey方可保留数据,否则本地数据会被清空,参考这个问题 - 关闭iCloud同步后,如果切换到
NSPersistentContainer数据仍在向云端同步,请参考下方代码中func autoMerge(isCloud: Bool)的实现。
代码:
Xcode自带的转义字符**,请自行处理。
import CoreData
extension NSManagedObjectContext {
// 提供访问接口以操作数据库
static var shared: NSManagedObjectContext {
CoreData.persistentContainer.viewContext
}
}
extension CoreData {
// 检测iCloud网盘权限
static var iCloudAvaliable: Bool {
let fileManager = FileManager.default
if let _ = fileManager.ubiquityIdentityToken {
// 只有iCloud云盘和APP的iCloud授权都开启的情况下才可用
return true
}
return false
}
static var cloudIsOpen: Bool {
// 用户开启了自动备份,且iCloud服务可用
iCloudAvaliable && CoreData.autoBackupWithCloud
}
// 记录用户选择的开启状态
@BoolUD("CoreData.autoMergeWithCloud")
static var autoBackupWithCloud: Bool
static var persistentContainer: NSPersistentContainer {
if #available(iOS 13.0, *) {
guard cloudIsOpen else {
return LocalPersistentContainer.shared
}
return CloudPersistentContainer.shared
} else {
return LocalPersistentContainer.shared
}
}
}
/// 本地持久化
struct LocalPersistentContainer {
static var shared: NSPersistentContainer = {
let container = NSPersistentContainer(name: "NoteData")
container.autoMerge(isCloud: false)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error (error), (error.userInfo)")
}
})
return container
}()
}
/// 云端持久化
@available(iOS 13.0, *)
struct CloudPersistentContainer {
static var shared: NSPersistentCloudKitContainer = {
newContainer()
}()
static func newContainer() -> NSPersistentCloudKitContainer {
// 数据库文件名
let container = NSPersistentCloudKitContainer(name: "NoteData")
container.autoMerge(isCloud: true)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
// container.viewContext.mergeConflictDelegate = YourCustomConflictDelegate()
if let error = error as NSError? {
#if DEBUG
fatalError("Unresolved error (error), (error.userInfo)")
#endif
}
})
do {
try container.initializeCloudKitSchema(options: [.dryRun, ])
} catch {
print("Unable to initialize cloudkit schema (error.localizedDescription)")
#if DEBUG
// fatalError("Unresolved error (error), (error.localizedDescription)")
#endif
}
// 将当前更改与云端更改隔离
// do {
// try container.viewContext.setQueryGenerationFrom(.current)
// } catch {
// fatalError("Failed to pin viewContext to the current generation:(error)")
// }
return container
}
}
extension NSPersistentContainer {
func autoMerge(isCloud: Bool) {
// 关闭同iCloud同步后必须启用历史追踪,否则闪退
let description = persistentStoreDescriptions.first
viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
viewContext.automaticallyMergesChangesFromParent = isCloud
if #available(iOS 13.0, *) {
if isCloud {
// 自动同步,如果本地NSPersistentContainer也设置这个,那么切换到本地后仍会跟云端做同步
description?.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.xxxxxxxxxxxx") // 这里需要修改为你的容器ID
description?.setOption(true as NSNumber,
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
} else {
// Fallback on earlier versions
}
// 这一步可以在关闭iCloud访问后保留数据,否则数据会被清空直至iCloud服务再次被启用
description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
}
}