这篇笔记主要是参考苹果官方文档的配置流程来介绍 CoreData 如何配置支持 CloudKit,按步骤介绍一下整体的流程
# 创建工程
在创建工程的时候勾选使用 CoreData,以及 Host in iCloud,这样会在初始化 CoreData Stack 的时候直接使用 NSPersistentCloudKitContainer
进行初始化,NSPersistentCloudKitContainer
是 NSPersistentContainer
子类,提供了管理 CloudKit 支持的存储和非云端的存储。
对于已有的使用 CoreData 工程来说,也就是替换 NSPersistentContainer
为 NSPersistentCloudKitContainer
。
对应的调试设备应该登录 iCloud 账户,并开启 iCloud Drive
# 配置工程
主要是一下两个配置
- 添加 iCloud Capability。然后在工程 Target 的 Signing & Capabilities → iCloud 部分开启 CloudKit 支持。并配置默认 Container
- 增加后台模式的支持。Background Modes → Remote notifications。这个目的是为了能实时收到云端数据变化的通知。
# 创建数据 Model
和正常创建数据模型一样,在 CoreData 数据模型里创建对应的 Entity,如下
这里创建了 CloudEntity
和 LocalEntity
,目的是只将本地数据的一部分镜像到 CloudKit,另外一部分只用本地存储。所以我们需要对创建的 model 进行配置,然后在不同的存储中组织数据,并决定将哪些存储同步到云端。
默认情况下是只有 Default 配置,这里我们增加了 Local 和 Cloud 的配置,Local 即是本地存储的配置,Cloud 是云端存储的配置。添加配置方式在 Editor → Add Configuration。
将需要同步到云端的数据实例(Entity)拖拽到对应 Cloud 配置里,本地数据实例同理拖拽到 Local 配置下。区分云端存储和非云端储存的工程端的配置如下
# 配置 PersistentContainer
这部分直接上代码
// 本地 Store 文件
func localStorePath() -> URL {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
return NSURL.init(fileURLWithPath: "\(path)/local.store", isDirectory: false) as URL
}
// 云端 Store 文件
func cloudStorePath() -> URL {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
return NSURL.init(fileURLWithPath: "\(path)/cloud.store", isDirectory: false) as URL
}
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "iCloudDemo")
//本地存储描述
let localStoreDescription = NSPersistentStoreDescription(url: self.localStorePath())
localStoreDescription.configuration = "Local"
//云端存储描述
let cloudStoreDescription = NSPersistentStoreDescription(url: self.cloudStorePath())
cloudStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions.init(containerIdentifier: "iCloud.com.perfectworld.cloudkitdemo") //工程配置中配置的 containerId
cloudStoreDescription.configuration = "Cloud" //实例配置部分的名字
//为 persistentContainer 配置两个存储描述
container.persistentStoreDescriptions = [
localStoreDescription,
cloudStoreDescription
]
#if DEBUG
do {
try container.initializeCloudKitSchema(options: [])
} catch {
// Handle any errors.
}
#endif
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
上面代码中容易出问题的地方是
配置文件需要是 file:// 格式的,否则会报错
CoreData SQL stores only support file URLs
文件存储的位置自己指定,如果只用本地存储的话,CoreData 是默认将文件存在了 Library/Application\ Support 下的,这里是指定位置存储,放在了 Documents 目录下,存储后的沙盒目录节奏如下,之前配置的 local.store (opens new window) 和 cloud.store (opens new window) 是作为单独的文件存在的。不过有意思的事儿,local.store 里还是有 CloudEntity 表的,只不过表里没有数据,只有 LocalEntity 表里有数据。
不支持 CloudEntity 和 LocalEntity 同在一个存储文件的做法, 同一个存储文件反复添加会触发 "Can't add the same store twice" 的错误
成功配置之后终端会有如下输出
<NSCloudKitMirroringDelegate: 0x7f9699d29a90>: Successfully set up CloudKit
integration for store
# 编写存储相关代码
代码如下
func storeCloudEntity() {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
let entity = CloudEntity(context: appDelegate!.persistentContainer.viewContext)
entity.cloudage = 18
entity.cloudname = "hello"
appDelegate?.persistentContainer.viewContext.insert(entity)
}
func storeLocalEntity() {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
let entity = LocalEntity(context: appDelegate!.persistentContainer.viewContext)
entity.localage = 20
entity.localname = "localhello"
appDelegate?.persistentContainer.viewContext.insert(entity)
try! appDelegate?.persistentContainer.viewContext.save()
}
触发对应的方法后查看保存效果
**云端数据查看。**点击工程配置里的 iCloud 部分中的 CloudKit Console 进入后端服务台查看存储数据
本地数据查看。
sqlite> .tables ZCLOUDENTITY ZLOCALENTITY Z_METADATA Z_MODELCACHE Z_PRIMARYKEY sqlite> select * from ZLOCALENTITY; 1|2|1|20|localhello
这里遇到的一些问题
- 在配置哪些表往云端存储的时候,如果表之前配置的是在Cloud 配置中,后面又改成 Local 配置,再次使用的时候会报错
The model configuration used to open the store is incompatible with the one that was used to create the store.
解决方法就是清缓存,可以参考 StackOverflow 上的回答 (opens new window)。重新调整后也可以重置云端的开发环境,重置后会丢失掉开发中的数据。 - 在查找云端数据的时候,比如在上图 QueryRecords 的时候首次会失败
Field 'recordName' is not marked queryable
,需要在对应的 Entity(或者叫Record) 的 Indexes 上添加 recordName,参考 StackOverflow 上的回答 (opens new window)。 - 查找云端数据的时候 DB 要选择 PrivateDatabase,Zone 是
com.apple.coredata.cloudkit.zone
。如果你刚 Save 完的话,云端是没有com.apple.coredata.cloudkit.zone
的,只有一个_defaultZone
,这俩 zone 是不一样的,com.apple.coredata.cloudkit.zone
需要一点时间出现。关于这部分内容官方也提供了一个指南 Managing iCloud Containers with the CloudKit Database App (opens new window)。 - iCloud 同步如果是用模拟器的话有手动触发的选项,非常快能看到远端的数据。
- 上线的时候需要在 iCloud 后台将服务部署在 Production 环境上,否则还是没有办法同步。
基本上整体配置和开发流程就是这样,后面还会涉及到数据的同步对界面的变化,后面再说。
参考地址:
相关链接: