2019年、苹果为Core Data带了一项重大的更新——引入NSPersistentCloudKitContainer。 这意味着无需编写大量代码,使用Core Data with CloudKit可以让用户在他所有的苹果设备上无缝访问应用程序中的数据。
Core Data为开发具有结构化数据的应用程序提供了强大的对象图管理功能。CloudKit允许用户在登录其iCloud账户的每台设备上访问他们的数据,同时提供一个始终可用的备份服务。Core Data with CloudKit则结合了本地持久化+云备份和网络分发的优点。
苹果持续对Core Data with CloudKit进行了强化,在最初仅支持私有数据库同步的基础上,添加了公有数据库同步以及共享数据库同步的功能。
Core Data
Core Data诞生于2005年,它的前身EOF在1994年便已经获得的不少用户的认可。经过了多年的演进,Core Data已经发展的相当成熟。 Core Data的功能包括但不限于:管理序列化版本、管理对象生命周期、对象图管理、SQL隔离、处理变更、持久化数据、数据内存优化以及数据查询等。 Core Data提供的功能繁多,但对于初学者并不十分友好,拥有陡峭的学习曲线。最近几年苹果也注意到了这个问题,通过添加PersistentContainer极大的降低了Stack创建的难度;SwiftUI及Core Data模版的出现让初学者也可以较轻松地在项目中使用其强大的功能了。
CloudKit
在苹果推出iCloud之后的几年中,开发者都无法将自己的应用程序同iCloud结合起来。这个问题直到2014年苹果推出了CloudKit框架后才得到解决。 CloudKit是数据库、文件存储、用户认证系统的集合服务,提供了在应用程序和iCloud容器之间的移动数据接口。用户可以在多个设备上访问保存在iCloud上的数据。
Core Data对象 vs CloudKit对象
两个框架都有各自的基础对象类型,相互之间并不能被一一对应。在此对一些基础对象类型做简单的介绍和比较:
NSPersistentContainer vs CKContainer
NSPersistentContainer通过处理托管对象模型(NSManagedObjectModel),对持久性协调器(NSPersistentStoreCoordinator)和托管对象上下文(NSManagedObjectContext)进行统一的创建和管理。开发者通过代码创建其的实例。
CKContainer则和应用程序的沙盒逻辑类似,在其中可以保存结构化数据、文件等多种资源。每个使用CloudKit的应用程序应有一个属于自己的CKContainer(通过配置,一个应用程序可以对应多个CKContainer,一个CKContainer 也可以服务于多个应用程序)。开发者通常不会在代码中直接创建新的CKConttainer,一般通过iCoud控制台或在Xcode Target的Signing&Capabilities中创建。
NSPersistentStore vs CKDatabase/CkRecordZone
NSPersistentStore是所有 Core Data 持久存储的抽象基类,支持四种持久化的类型(SQLite、Binary、XML 和 In-Memory)。在一个NSPersistentContainer中,通过声明多个的NSPersistentStoreDescription,可以持有多个NSPersistentStore实例(可以是不同的类型)。NSPersistentStore没有用户鉴权的概念,但可以设置只读或读写两种模式。由于Core Data with CloudKit需要持久化历史追踪的支持,因此只能同步将SQLite作为存储类型的NSPersistentStore,在设备上,该NSPersistentStore的实例将指向一个SQLite数据库文件。
在CloudKit上,结构化的数据存储只有一种类型,但采用了两个维度对数据进行了区分。 从用户鉴权角度,CKDatabase分别提供了三种形式的数据库:私有数据库、公有数据库、共享数据库。应用程序的使用者(已经登录了iCloud账号)只能访问自己的私有数据库,该数据库的数据保存在用户个人的iCloud空间中,其他人都不可以对其数据进行操作。在公共数据库中保存的数据可以被任何授权过的应用程序调用,即使app的使用者没有登录iCloud账户,应用程序仍然可以读取其中的内容。应用程序的使用者,可以将部分数据共享给其他的同一个app的使用者,共享的数据将被放置在共享数据库中,共享者可以设置其他用户对于数据的读写权限。 数据在CKDatabase中也不是以零散的方式放置在一起的,它们被放置在指定的RecoreZone中。我们可以在私有数据库中创建任意多的Zone(公共数据库和共享数据库只支持默认Zone)。当CKContainer被创建后,每种数据库中都会默认生成一个名为_defaultZone的CKRecoreZone。 因此,当我们保存数据到CloudKit数据库时,不仅需要指明数据库(私有、公有、共享)类型,同时也需要标明具体的zoneID(当保存到_defaultZone时无需标记)。
NSManagedObjectModel vs Schema
NSManagedObjectModel是托管对象模型,标示着Core Data对应的数据实体(Enities)。绝大多数情况下,开发者都是使用Xcode的Data Model Editor来对其进行的定义,定义会被保存在xcdatamodeled文件中,其中包含了实体属性、关系、索引、约束、校验、配置等等信息。
当在应用程序中启用CloudKit后,将在CKContainer创建一个Schema。Schema中包括记录类型(Record Type)、记录类型类型之间可能存在的关系、索引以及用户权限。 除了直接在iCloud控制台创建Schema的内容外,也可以通过在代码中创建CKRecord,让CloudKit自动为我们创建或更新Schema中对应的内容。 Schema中有权限的设定(Security Roles),可以分别为world、icloud以及creator设定不同的读写权限。
Entities vs Record Types
尽管我们通常会强调Core Data不是数据库,但实体(Enitities)与数据库中的表非常相似。我们在实体中描述对象,包括其名称、属性和关系。最终将其描述成NSEntityDescription并汇总到NSManagedObjectModel中。
在CloudKit中用Record Types描述数据对象的名称、属性。Enitiy中有大量的信息可以配置,但Record Types只能对应描述其中的一部分。由于两方无法一一对应,因此在设计Core Data with CloudKit的数据对象时要遵守相关规定。
Managed Object vs CKRecord
托管对象(Managed Object)是表示持久存储记录的模型对象。托管对象是NSManagedObject或其子类的实例。托管对象在托管对象上下文(NSManagedObjectContext)中注册。在任何给定的上下文中,托管对象最多有一个实例对应于持久存储中的给定记录。
在CloudKit上,每条记录被称作为CKRecord。我们不需要关心Managed Object的ID(NSMangedObjectID)的创建过程,Core Data将为我们处理一切,但对于CKRecord,多数情况下,我们需要在代码中明确为每条记录设定CKRecordIdentifier。作为CKRecord的唯一标识,CKRecordIdentifier被用于确定该CKRecord在数据库的唯一位置。如果数据保存在自定义的CKRecordZone,我们也需要在CKRecord.ID中指明。
CKSubscription
CloudKit是云端服务,它要同一iCloud账户的不同设备(私有数据库)或者使用不同iCloud账号的设备(公共数据库)的数据变化做出相应的反馈。
开发者通过CloudKit在iCloud上创建CKSubscription,当CKContainer中的数据发生变化时,云端服务器会检查该变化是否满足某个CKSubscription的触发条件,在条件满足时,对订阅的设备发送远程提醒(Remote Notification)。这就是当我们在Xcode Target的Signing&Capabilities中添加上CloudKit功能时,会Xcode自动添加Remote Notification的原因。 在实际使用中,需要通过CKSubscription的三个子类完成不同的订阅任务: CKQuerySubscription,当某个CKRecord满足设定的NSPercidate时推送Notification。 CKDatabaseSubscription,订阅并跟踪数据库(CKDatabase)中记录的创建、修改和删除。该订阅只能用于私有数据库和共享数据库中自定义的CKRecordZone,并只会通知订阅的创建者。