版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.10.21 星期一 |
前言
将结构化app和用户数据存储在可由应用程序的所有用户共享的iCloud容器中。感兴趣的可以看下面几篇文章。
1. CloudKit框架详细解析(一) —— 基本概览(一)
开始
题外话:徐S我想你了,虽然我们不会再相逢,你也不会再看见,但是很想你,你定你的亲,我单我的身,互不打扰~~
接着看下主要内容:
主要内容:在本CloudKit教程中,您将学习如何在应用程序中的iCloud中添加和查询数据,以及如何使用
CloudKit
仪表板管理数据。翻译文章来自下面地址
接着就是写作环境
Swift 5, iOS 13, Xcode 11
下面进入正题
CloudKit
是Apple的远程数据存储服务。它基于iCloud,提供了一种低成本的选项,可以使用用户的iCloud
帐户作为后端存储服务来存储和共享应用程序数据。
CloudKit
有两个主要组件:
- web dashboard,用于管理记录类型和任何公共数据。
- 一组用于在iCloud和设备之间传输数据的API。
使用CloudKit
,由于用户只能访问自己的私有数据库,而无法查看其他任何用户的私有数据,因此可以完全保护用户的私有数据。
对于使用大量数据但不需要大量服务器端逻辑的仅限iOS的应用程序,CloudKit
是一个不错的选择。此外,您可以将CloudKit
用于Web
和server
应用程序。
在本CloudKit
教程中,您将通过创建一个名为BabiFüd的餐厅评分应用来获得使用CloudKit
的动手经验。
注意:要使用此CloudKit教程中的示例应用程序,您需要一个有效的iOS开发人员帐户。没有一个,您将无法启用
iCloud
权利或访问CloudKit dashboard
。
Why CloudKit?
您可能想知道为什么应该选择CloudKit
而不是其他商业BaaS(Backend as a Service)
产品,甚至推出自己的服务器。
有以下三个原因:Simplicity, trust and cost
。
1. Simplicity
与其他后端解决方案不同,CloudKit
不需要任何设置。您无需选择,配置或安装服务器。苹果处理安全性和扩展性。
只需注册iOS开发者计划,您就有资格使用CloudKit
。您无需注册其他服务或创建新帐户。在您的应用中启用CloudKit
功能后,所有必要的服务器设置过程都会自动发生。
无需下载其他库并进行配置;您可以像其他任何iOS框架一样导入CloudKit
。 CloudKit
框架本身还通过提供用于常见操作的便捷API来提供一定程度的简化。
对用户来说也很容易。由于CloudKit
使用用户在设置设备时输入的iCloud
凭据(或在设置后通过Settings
应用程序输入的iCloud
凭据),因此无需构建复杂的登录屏幕。只要他们登录,用户就可以无缝使用您的应用程序。那应该使您进入Cloud 9
!
2. Trust
CloudKit
的另一个好处是,用户可以依靠Apple
而不是应用程序开发人员来信任其数据的隐私和安全性。 CloudKit
隔离了您(开发人员)的用户数据。
尽管这种缺乏访问权限的情况在调试时可能会令人沮丧,但由于您不必担心安全性或说服用户其数据是安全的,因此这是一个不错的选择。 如果应用程序用户信任iCloud,那么他们也可以信任您。
3. Cost
最后,对于任何开发人员而言,运行服务的成本都是一笔不小的数目。 甚至最便宜的服务器主机也无法为小型,免费或廉价应用提供低成本解决方案。 因此,始终会有与运行应用程序相关的成本。
借助CloudKit
,您可以免费获得合理数量的公共数据存储和数据传输。 Apple的CloudKit site网站上有一个方便的计算器。
这些优势使CloudKit
服务成为Mac和iOS应用程序的低麻烦解决方案。
Introducing BabiFüd
此CloudKit
教程的示例应用程序BabiFüd
是对标准“评价餐馆”应用程序的最新尝试。 用户没有根据食物质量,服务速度或价格来评论餐厅,而是对儿童友善性进行了评分。 这包括更衣设施,加高座椅和健康食品的可用性。
该应用程序包含两个选项卡:附近餐厅列表和用户生成的注释。 您可以在下面查看运行中的应用程序。
模型类支持这些视图,并包装对CloudKit
的调用。 CloudKit对象称为records
。 模型中的主要记录类型是Establishment
,它代表您应用中的各种餐馆。
打开入门项目。
您必须先更改应用的包标识符和团队,然后才能开始编码。 您需要设置团队以从Apple获得必要的权限,同时拥有唯一的包标识符使整个过程变得更加容易。
在Xcode
中打开BabiFud.xcodeproj
。 在项目导航器中选择BabiFud
项目,然后选择BabiFud target
。 选择General
选项卡后,将Bundle Identifier
替换为唯一的东西。 标准做法是使用反向域名表示法并包括项目名称。
这将考虑Bundle Identifier
。 现在,您需要为CloudKit
设置应用,并创建一些容器来保存数据。
Entitlements and Containers
您需要一个容器来保存应用程序的记录,然后才能通过应用程序添加任何数据。 容器是服务器上所有应用程序数据的概念位置的术语。 它是一组公共和私有数据库。
要创建容器,您首先需要为您的应用启用iCloud权利:
- 1) 在目标编辑器中选择
Signing & Capabilities
选项卡。 - 2) 单击
+ Capability
。 - 3) 选择
iCloud
。 - 4) 从下拉列表中选择您的团队。
此时,Xcode
可能会提示您输入与您的iOS开发者帐户关联的Apple ID
。 如果是这样,则按要求输入。
接下来,通过选中Services
组中的CloudKit
复选框来启用CloudKit
。
最后,如果CloudKit
不会自动为您创建一个容器,请单击Containers
下的+
以添加新容器。
在弹出窗口中,添加您的bundle identifier
。
这将创建一个名为iCloud.<your app’s bundle ID>
的默认容器。
注意:在撰写本文时,无法删除
CloudKit
容器。请记住,如果为本教程创建一个新容器,它将永久存在于CloudKit Dashboard
中。
1. Troubleshooting iCloud Setup in Xcode
如果在创建entitlements
,构建项目或运行应用程序时看到与容器ID相关的任何警告或错误,则以下是一些故障排除提示:
- 如果
iCloud
部分的Steps
组中显示了任何警告或错误,请尝试按Fix Issue
按钮。您可能需要执行几次。 - 该应用程序的
bundle ID
和iCloud
容器必须匹配,并且它们必须存在于开发者帐户中。例如,如果bundle identifier
为com.<your domain>.BabiFud
,则iCloud
容器名称应为iCloud
。加上bundle id
:iCloud.com.<your domain>.BabiFud
。 -
iCloud
容器名称必须唯一,因为这是CloudKit
用于访问数据的全局标识符。由于iCloud容器名称包含bundle ID
,因此bundle ID
也必须是唯一的。这就是为什么您不能使用com.raywenderlich.BabiFud
并且必须更早对其进行更改的原因。 - 为了
entitlements
起作用,必须在Certificates, Identifiers and Profiles
门户的App IDs
部分中显示app/bundle ID
。这意味着用于签署应用程序的证书必须来自设置的team ID
,并且必须列出app ID
,这也意味着iCloud container ID
。
通常,如果您登录到有效的开发人员帐户,Xcode会自动执行所有这些操作。不幸的是,这有时会不同步。
它可以帮助以新的ID开头,并使用iCloud
功能窗格更改CloudKit container ID
以匹配。否则,您可能必须编辑Info.plist
或BabiFud.entitlements
文件,以确保其中的ID值反映您为bundle ID
设置的内容。
Introducing the CloudKit Dashboard
下一步是创建一些记录类型,这些记录类型定义应用程序将使用的数据。 您可以使用CloudKit Dashboard
执行此操作。 单击CloudKit Dashboard
,您可以在目标的Signing & Capabilities
窗格中的iCloud
下,或通过在浏览器中打开https://icloud.developer.apple.com/dashboard/来找到它。
注意:有时,新应用程序的容器可能需要一段时间才能显示在信息中心中。 如果您没有立即看到容器,请等待几分钟,然后重试。
仪表盘的外观如下:
CloudKit dashboard
包含六个部分:
Data
Schema
Telemetry
Usage
Logs
API Access
Schema
部分表示CloudKit
容器的高级对象:Record Types, Indexes, Security Roles and Subscription Types
。在本教程中,您将只处理Record Types
。
Record Types
是定义单个记录的一组字段。在面向对象编程方面,Record Types
就像一个类。您可以将一条记录视为特定Record Types
的实例。它表示容器中的结构化数据,非常类似于数据库中的典型行,并且封装了一系列键/值对。
在Data
下的Private Database
和Public Database
部分中,您可以向有权访问的数据库中的数据添加数据或在其中搜索数据。请记住,作为开发人员,您可以访问所有公共数据,但只能访问自己的私有数据。
User Records
存储有关当前iCloud
用户的数据,例如名称和电子邮件。
您可以使用Record Zone
(在此处称为Default Zone
)通过将记录分组在一起为私有数据库提供逻辑组织。
自定义区域允许您在处理其他操作之前同时保存多个记录,从而支持原子事务。自定义区域不在本教程的讨论范围之内。
API Access
部分提供了为团队成员配置仪表板权限的功能。如果您有多个开发团队成员,则可以在此处限制他们编辑数据的能力。这也超出了本教程的范围。
Adding the Establishment Record Type
暂时考虑一下应用程序的设计。 您跟踪的每个establishments
都有大量数据:便于儿童使用的选项的名称,位置和可用性。 记录类型使用字段来定义每个记录包含的各种数据。
在Schema
下,选择Record Types
,然后选择New Type
以添加新的记录类型。
将新记录类型命名为Establishment
,然后按Enter
。
您会看到一行系统字段,它们是为每种记录类型自动创建的。 每个字段都有一个Field Name
,一个Field Type
和一个Index
。
当然,您可以添加自己的字段。 首先选择Add Field
,将其命名为name
,然后将Field Type
设置为String
。 重复直到添加了所有这些字段:
单击页面底部的Save
以保存新的记录类型。
接下来,单击Edit Indexes
。 您将添加两个索引,以便可以查询记录:
- 1) 索引类型为
QUERYABLE
的recordName
。 - 2) 以索引类型
QUERYABLE
命名name
。
默认情况下,您需要使recordName
可查询。 您将name
设置为可查询,以便您可以基于特定名称查询记录。
单击页面底部的Save Changes
以保存索引。
完成后,您的字段列表应如下所示:
现在,您可以将一些样本establishment
记录添加到数据库中。 为此,请在信息中心顶部的下拉菜单中切换到Data
:
在左侧导航窗格的Public Database
部分下,选择_defaultZone
。 该区域将包含您应用的公共记录。
如果尚未选择Establishment
记录类型,则从中央窗格的下拉列表中选择它。 然后单击底部详细信息窗格中的New Record
按钮,如下面的屏幕快照所示:
这将创建一个新的空的Establishment
记录。
此时,您就可以为您的应用输入一些测试数据了。
以下示例establishment
数据是虚构的。 这些establishment
位于Apple
总部附近,因此可以在模拟器中轻松找到它们。
输入每个记录,如下所述:
您可以在包含先前入门项目的材料文件中找到图像。 查找Images
文件夹。
保存所有记录后,dashboard
应如下所示。 您可能需要单击Query Records
按钮以使记录出现:
对于每个记录,输入的值是数据的数据库表示形式。
在应用程序方面,数据类型不同。 例如,ChangeingTable
是一个枚举。 因此,为changetable
指定的Int
值可能对应于男女房间中可用的一个变化表。
对于HealthyOption
和kidsMenu
,Int
值表示布尔类型:0
表示establishment
不具有该选项,而1
表示它具有。
要运行该应用程序,您需要拥有一个可用于开发的iCloud
帐户。 您将在此处找到说明: Creating an iCloud Account for Development。
您还需要在iOS模拟器中输入与此帐户关联的iCloud credentials
:Enter iCloud Credentials Before Running Your App
返回到Xcode。 现在该开始将这些数据集成到您的应用程序中了!
1. Querying Establishment Records
要从数据库中选择记录,您将使用CKQuery
。 CKQuery
描述了如何查找与某些条件匹配的特定类型的所有记录。这些条件可以是“具有“ M”开头的“名称字段”的所有记录”,“具有加高座位的所有记录”或“ 3公里内的所有记录”。
iOS使用NSPredicate
处理此类表达式。 NSPredicate
评估对象以查看它们是否符合条件。Predicates
也用在Core Data
中;它们自然适合CloudKit
,因为谓词的定义只是对字段的比较。
CloudKit
仅支持可用NSPredicate
函数的子集。其中包括数学比较,一些字符串和设置操作,例如“字段匹配列表中的一项”,以及特殊的距离函数。 CKQuery Class Reference包含CloudKit
支持的函数的详细列表以及如何使用它们的描述。
在Xcode中,打开Model.swift
。用以下实现替换refresh(_ :)
:
@objc func refresh(_ completion: @escaping (Error?) -> Void) {
// 1.
let predicate = NSPredicate(value: true)
// 2.
let query = CKQuery(recordType: "Establishment", predicate: predicate)
establishments(forQuery: query, completion)
}
这是您添加的内容:
- 1) 您创建一个值为
true
的谓词。NSPredicate
确定如何获取或过滤数据。 在这种情况下,您要指定一个应该刚刚存在的值。 - 2) 您添加查询以指定所需的记录类型和谓词。
接下来,用以下内容替换establishments(forQuery:_ :)
:
private func establishments(forQuery query: CKQuery,
_ completion: @escaping (Error?) -> Void) {
publicDB.perform(query,
inZoneWith: CKRecordZone.default().zoneID) { [weak self] results, error in
guard let self = self else { return }
if let error = error {
DispatchQueue.main.async {
completion(error)
}
return
}
guard let results = results else { return }
self.establishments = results.compactMap {
Establishment(record: $0, database: self.publicDB)
}
DispatchQueue.main.async {
completion(nil)
}
}
}
由于您在此处传递CKQuery
对象,因此您的公共数据库publicDB
可以执行查询。 如果回想一下在CloudKit
仪表板中创建两个Establishment
对象时,可以将它们放在默认容器中的公共数据库中。 这正是inZoneWith
参数在此处指定的内容。 现在,数据库将查询公共数据库中存在的所有Establishment
记录。
构建并运行。 您应该会看到附近的establishments
列表。
事情看起来不太正确。 您在表中有两项,并且如果选择一项,则大多数详细信息将正确加载。
但是,详细信息屏幕缺少图像。 那是因为您上传的图像是CKAssets
。 这些需要一些特殊的处理。
Working With Binary Assets
资源asset
是与记录关联的二进制数据,例如图像。 就您而言,您应用的资源是在NearableTableViewController
的表格视图中显示的establishment
照片。
在本部分中,您将添加逻辑以加载在检索establishment
记录时下载的资产。
打开Establishment.swift
并将loadCoverPhoto(_ :)
替换为以下代码:
func loadCoverPhoto(completion: @escaping (_ photo: UIImage?) -> ()) {
// 1.
DispatchQueue.global(qos: .utility).async {
var image: UIImage?
// 5.
defer {
DispatchQueue.main.async {
completion(image)
}
}
// 2.
guard
let coverPhoto = self.coverPhoto,
let fileURL = coverPhoto.fileURL
else {
return
}
let imageData: Data
do {
// 3.
imageData = try Data(contentsOf: fileURL)
} catch {
return
}
// 4.
image = UIImage(data: imageData)
}
}
此方法从asset attribute
加载图像,如下所示:
- 1) 尽管在检索记录的其余部分的同时下载了资源,但您还是要异步加载图像。 因此,将所有内容包装在
DispatchQueue.async
块中。 - 2) 检查以确保资产
CoverPhoto
存在并且具有fileURL
。 - 3) 下载图片的二进制数据。
- 4) 使用图像数据创建
UIImage
的实例。 - 5) 对检索到的图像执行完成回调。 请注意,无论执行哪个
return
,都会执行defer
块。 例如,如果没有图像资产,那么return
时就不会设置image
,并且餐厅也不会显示图像。
构建并运行。 现在将显示establishment
图像。 很好!
CloudKit
资产有两个陷阱:
- 1) 资产只能在
CloudKit
中作为记录中的属性存在; 您不能单独存储它们。 删除记录还将删除所有关联资产。 - 2) 检索资产可能会对性能产生负面影响,因为您与其余记录数据同时下载了资产。 如果您的应用大量使用资产,则应存储对仅包含资产的另一种记录类型的引用。
Relationships
了解如何在CloudKit
中创建不同记录类型之间的关系非常重要。 为此,您将添加一个新的记录类型Note
,并创建不在公共数据库中的私人记录。 这些记录将属于一个Establishment
。
返回CloudKit
仪表板,通过进入Schema
并选择New Type
来添加Note
类型。 添加以下字段,然后保存:
单击Edit Indexes
,然后单击Add Index
以使recordName
可查询。
接下来,将一个新字段添加到Establishment
中:
通过在Establishment
上创建字段notes
,并在notes
上创建Establishment
,您现在具有一对多关系。 这意味着一个Establishment
可以有多个注释notes
,但是一个注释只能属于一个Establishment
。
在继续之前,您需要获取Establishment
记录的Name
值。 在CloudKit
仪表板中,返回到Data
,选择Public Database
,然后从下拉列表中键入Establishment
。 接下来,单击Query Records
。 记下第一项的Name
,如下所示:
接下来,就像在两个Establishment
记录中一样,在CloudKit
仪表板中创建一个Note
记录。 仍在仪表板的Data
部分中,从Type
下拉列表中选择Note
。 但是,将Public Database
更改为Private Database
,然后选择New Record
。 现在,您的记录将仅在CloudKit
数据库中可用。 然后更改以下值:
- 对于
Establishment
,输入在名称字段中找到的值。 - 输入您想要输入的文字。
保存之前,请复制在新note’s
的Name
字段中找到的值,以简化下一步操作。
您的新记录应如下所示:
选择Save
。 接下来,查询您的公共场所public Establishments
并编辑您用于注释的Name
的记录。 选择+
按钮,输入您在上一步中保存的注释名称,然后Save
。 它看起来应该像这样:
现在,您有一个public Establishment
记录,该记录与一个private Note record
有关系! 要加载注释,请打开Note.swift
,并将fetchNotes(_ :)
替换为以下内容:
static func fetchNotes(_ completion: @escaping (Result<[Note], Error>) -> Void) {
let query = CKQuery(recordType: "Note",
predicate: NSPredicate(value: true))
let container = CKContainer.default()
container.privateCloudDatabase
.perform(query, inZoneWith: nil) { results, error in
}
}
这看起来类似于您查询和下载establishments
的方式。 但是,请注意,该信息现在是从privateCloudDatabase
而不是从公共数据库加载的。 只需简单地指定您要在应用中使用特定于用户的数据还是公开数据。
接下来,在闭包内部添加以下内容以获取记录的数据,就像您之前对Establishment
所做的一样:
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard let results = results else {
DispatchQueue.main.async {
let error = NSError(
domain: "com.babifud", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Could not download notes"])
completion(.failure(error))
}
return
}
let notes = results.map(Note.init)
DispatchQueue.main.async {
completion(.success(notes))
}
但是,此代码仅适用于加载所有用户注释。 要加载与场所相关的注释,请打开Establishment.swift
,并将以下内容添加到init?(record:database :)
的末尾:
if let noteRecords = record["notes"] as? [CKRecord.Reference] {
Note.fetchNotes(for: noteRecords) { notes in
self.notes = notes
}
}
这将检查您的establishment
是否具有引用数组,然后仅加载那些特定记录。 打开Note.swift
并添加以下方法:
static func fetchNotes(for references: [CKRecord.Reference],
_ completion: @escaping ([Note]) -> Void) {
let recordIDs = references.map { $0.recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
operation.fetchRecordsCompletionBlock = { records, error in
let notes = records?.values.map(Note.init) ?? []
DispatchQueue.main.async {
completion(notes)
}
}
Model.currentModel.privateDB.add(operation)
}
使用CKFetchRecordsOperation
可以轻松地一次加载多个记录。 您使用ID
列表创建它,并设置其服务质量以确保其在后台运行。 然后,设置完成块以传递获取的注释,如果有错误,则传递一个空数组。 要运行该操作,请在私有数据库上调用add
。
构建并运行,然后转到Notes
选项卡。 您应该看到notes
已加载。
另外,转到设置note
的establishment
,然后选择Notes
。 您可以看到其他establishment
未加载注释。
注意:到目前为止,您无需登录
iCloud
即可运行该应用程序。 如果您在加载注释时遇到问题,请在Settings/Sign in to your iPhone
下登录。 请记住,您需要使用与登录CloudKit
仪表板相同的Apple ID
登录。 然后,尝试重新启动该应用程序以查看数据加载正确。
1. Troubleshooting Queries
如果数据显示不正确或根本不显示,请使用CloudKit
仪表板检查样本数据。 确保所有记录都存在,已将它们添加到默认区域,并且它们具有正确的值。
如果需要重新输入数据,可以通过单击垃圾桶图标删除记录。
调试CloudKit
错误可能很棘手。 CloudKit错误消息通常不包含大量信息。
要确定错误原因,您可以将错误代码与您尝试的特定数据库操作结合使用。 使用数字错误代码,查找匹配的CKErrorCode
枚举。 文档中的名称和说明将有助于缩小问题的原因。
注意:有关
CloudKit
可以返回的错误代码的列表,请阅读CKError Reference。
您也可以在 CloudKit上查看Apple的文档。
借助CloudKit
,您可以通过Apple提供的出色后端API将您的应用程序提升到一个新的水平。
后记
本篇主要讲述了CloudKit一个基本使用示例,感兴趣的给个赞或者关注~~~