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 全家桶)的数据同步。
基本概念
上面这张图出自 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
方法即可。因为是网络调用,因此要做好错误处理。
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 获取一条数据
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 查询满足条件的多条数据
let predicate = NSPredicate(format: "name BEGINSWITH 'Apple Store'")
let query = CKQuery(recordType: "Place", predicate: predicate)
publicDB.perform(query, inZoneWith: nil) { (results, error) in
// ...
}
修改数据
查询数据,修改,然后保存。
删除数据
过程跟 fetch 类似:
let recordID = CKRecordID(recordName: "GreatPlace")
publicDB.fetch(withRecordID: recordID) { (recordID, error) in
// handle errors here
}
订阅
我们可以通过 CKRecordZoneSubscription
或者 CKQuerySubscription
来订阅数据的变化,这样当数据发生变化时设备会收到推送。
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,可以很方便地进行数据的管理。