CoreData with CloudKit云端数据同步及开关切换

992 阅读2分钟

想做云端数据备份,调研一番后发现只需要在操作数据时用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)

}

}