CloudKit 上手

Baas

对于独立客户端开发者来说,维护一个服务器成本相当高:你需要学习服务端技能、租用服务器、考虑扩展问题、还得花精力去维护,还有一个微不足道的原因是租服务器要花钱。

幸亏 IT 巨头们已经帮我们解决了这个问题,把后端能力打包成一个服务,让开发者不需要操心服务器相关的任何事情,只需要直接调用 API,这就是传说中的 BaaS(Backend as a Service)。

典型的 BaaS 包含下面的功能:

  • 数据存储
  • Crash 统计
  • Analytics
  • 远程配置
  • 推送

强大如 Firebase 还有其他许多功能:

  • Dynamic Link(跨平台的 Universal Link)
  • AdMob 广告平台
  • Web 托管

实际上,如果你的 app 体量不大并且功能简单,可以不花钱享受上面的所有服务。但弊端是一旦体量大了,需要更多的服务端开发需求时,BaaS 就很难满足了。

平台选择

市面上已经有很多 BaaS 平台可选:

  • Google 的 Firebase:功能全面且强大,但由于是 Google 的服务,被 GFW 照顾得很好。实测 Analytics 和 AdMob 功能可用(后台需要翻墙),数据库功能和动态链接功能被墙。
  • LeanCloud:有存储、消息、分析功能,算国内名气比较大的 BaaS 提供商。
  • WildDog:主要致力于通信领域,了解不多。
  • CloudKit:苹果亲儿子,主要提供的是数据存储功能,可以在 iOS/macOS 和 Web 端访问。

选择 CloudKit 的原因一般有几个:

  • Apple 自家的服务,基本不用担心服务稳定性和公司哪天突然倒闭的问题。
  • 和 Apple ID 打通,如果不想搭建自己的账号系统,可以不用关心账户体系的问题。
  • Apple 和某组织关系处理得比较好,不会被照顾。

缺点也很明显:不支持安卓。

功能

CloudKit 的主要功能是存储数据(CURD)和监听数据变化,后者可以方便地实现多端(Apple 全家桶)的数据同步。

基本概念

CloudKit

上面这张图出自 WWDC 2017 的 keynote,很好地展示了 CloudKit 中的核心概念。

  • Apple 提供了两套数据库环境:Development 和 Production,两个环境功能相同但数据是隔离的,方便开发和测试。
  • 每个数据环境中包含三个数据库:
    • Public Database:任何人可读写。
    • Shared Database:可以用来实现用户间的数据共享,比如便签的共享。
    • Private Database:每个用户只能访问自己的数据,开发者无法访问。
  • 数据库内部可以包含多个 Zone,一般使用默认的 Zone 就足够了。
  • 每个 Zone 中包含若干的 Record,可以理解成数据中的一行,每条 Record 都有自己的 RecordType,用来描述每个字段的名称和类型。
  • Reference:Record 中的字段可以引用其他的 Record 来表达关系。

下面有一张术语对照表:

RDBMS LeanCloud CloudKit
Database Application Database
Table Class Zone
Row Object Record
Index Index Index
JOIN Reference Reference

使用

了解了上面的概念后就可以开始使用了。首先需要在 Xcode 中开启 iCloud 中的 CloudKit 功能。

存储数据

存储数据过程非常简单:获取一个 Public Database 的实例,创建一个 CKRecord,调用 saveRecord 方法即可。因为是网络调用,因此要做好错误处理。

1
2
3
4
5
6
7
8
let publicDB = CKContainer.default().publicCloudDatabase

let greatID = CKRecordID(recordName: "GreatPlace")
let place = CKRecord(recordType: "Place", recordID: greatID)

publicDB.save(place) { savedRecord, error in
// handle errors here
}

查询数据

我们有两种方式查询数据:

  • 通过 CKRecordID 获取一条数据
1
2
3
4
5
6
7
8
9
10
11
let recordID = CKRecordID(recordName: "GreatPlace")

publicDB.fetch(withRecordID: recordID) { (fetchedPlace, error) in
guard let fetchedPlace = fetchedPlace else {
// handle errors here
return
}

let name = fetchedPlace["name"] as? String ?? "Unnamed Place"
fetchedPlace["name"] = name + " Door A" as CKRecordValue
}
  • 通过 CKQuery 查询满足条件的多条数据
1
2
3
4
5
6
let predicate = NSPredicate(format: "name BEGINSWITH 'Apple Store'")
let query = CKQuery(recordType: "Place", predicate: predicate)

publicDB.perform(query, inZoneWith: nil) { (results, error) in
// ...
}

修改数据

查询数据,修改,然后保存。

删除数据

过程跟 fetch 类似:

1
2
3
4
5
let recordID = CKRecordID(recordName: "GreatPlace")

publicDB.fetch(withRecordID: recordID) { (recordID, error) in
// handle errors here
}

订阅

我们可以通过 CKRecordZoneSubscription 或者 CKQuerySubscription 来订阅数据的变化,这样当数据发生变化时设备会收到推送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let predicate = NSPredicate(format: "description CONTAINS 'party'")

let subscription = CKSubscription(recordType: "Checkin", predicate: predicate, options: .firesOnRecordCreation)

let info = CKNotificationInfo()
info.alertLocalizationKey = "NEW_PARTY_ALERT_KEY"
info.soundName = "NewAlert.aiff"
info.shouldBadge = true

subscription.notificationInfo = info

publicDB.save(subscription) { subscription, error in
//...
}

Apple 写了一篇详细的文档来演示如何在本地缓存 CloudKit 的数据。

后台

Apple 还提供了一个 Dashboard,可以很方便地进行数据的管理。

参考

给鸡排饭加个蛋