大多数浏览器和
Developer App 均支持流媒体播放。
-
CKTool JS 简介
了解如何利用 CKTool JS 管理与自动运行您的 iCloud 容器。我们将介绍如何配置 CKTool JS,以便管理您的容器的模式、轻松修改记录,以及即时处理数据。我们还将探索如何将 CKTool JS 集成到您的自动化和工具工作流程。为能更好地理解此讲座,我们建议您先熟悉 CloudKit 模式、JavaScript 和 npm。
资源
相关视频
WWDC22
WWDC21
-
下载
大家好 我是 Kent 是 CloudKit 团队的一名工程师 我很高兴向大家介绍一个新的库 您可以使用它来访问 CloudKit 首先我将介绍如何配置这个新库 然后您将学习如何管理您的架构 以及如何使用 CKTool JS 访问用户数据 我们开始吧
CloudKit 是一种持久性存储技术 可让您将 App 的数据 存储在容器中的 iCloud 中 通过在您的 App 中使用 CloudKit 您也可以 让您的数据 在设备和 web 上保持最新状态 要构建 App 您可以使用 Apple 平台上的 CloudKit 框架 或 web 上的 CloudKit JS 访问 iCloud 存储 为了实现自动化和工具化 Xcode 提供了用于 macOS 的 cktool 现在您有了一种使用 CKTool JS 自动化更改并与 iCloud 交互的 新方法
CKTool JS 允许您执行 与 Xcode 13 中引入的 cktool 命令行工具相同的操作 并支持类似的用例 实际上 CKTool JS 是用来 实现 CloudKit Console 中 添加记录类型和查询记录等功能的
使用 CKTool JS 您可以管理您的 App 容器 并执行架构操作 例如重置和应用更新到您的架构 这是以前用 JavaScript 无法做到的
CKTool JS 允许您使用 现有记录的唯一标识符 或通过复杂查询获取现有记录 它允许您创建新记录并更新它们 CKTool JS 为 TypeScript 提供了严格的类型定义 这些类型定义启用编译时检查 以标记客户端库的错误使用 并在支持的 IDE 中启用代码完成 因此您会发现 编辑 CKTool JS 代码更加容易
此外 这个新库附带了对 Node.js 和开箱即用浏览器的支持 CKTool JS 作为一组 npm 包分发 允许您将其集成为 JavaScript 构建管道的一部分 这样做可以启用诸如 摇树和捆绑之类的功能 您还可以跟踪这些包的更新 因为它们的发布历史记录 可以从 npm 透明地获取
以下软件包是 CKTool JS 发行版的一部分 请注意 这些包在 @Apple 范围内
并遵循在名称开头 使用 cktool. 的约定 您使用的主要软件包是 cktool.database 要启用与 iCloud 的通信 您还需要为您的目标平台 使用另一个包 用于 Node.js 的 cktool.target.nodejs 或用于 web 浏览器的 cktool.target.browser
cktool.database 会自动拉入另外三个包 cktool.core cktool.api.base 和 cktool.api.database 由于 CKTool JS 直接与 iCloud 通信 因此必须首先对其进行授权 根据要调用的操作 您需要管理令牌或用户令牌 这两种令牌都可以 从 CloudKit Console 获得
管理令牌用于访问管理操作 其作用域是团队和用户 这些操作包括启用架构导入和导出 架构验证以及将容器重置为生产容器 用户令牌的范围是团队和容器 并允许访问这些容器中的 私有用户数据 要了解如何获取这些授权令牌 以及与 CloudKit 的持续集成 请查看 WWDC21 的 “Automate CloudKit tests with cktool and declarative schema”
任何时候您想在脚本中 使用 CKTool JS 您首先都需要配置它以供使用 但是在开始配置 CKTool JS 之前 我将快速回顾一下 CloudKit 架构的组成 在 CloudKit 中 数据以结构化的方式存储 具有相同类型值的数据 作为记录存储在一起 记录是记录类型的实例 记录类型描述的记录属性 称为字段 除了您自定义的字段之外 CloudKit 还添加了系统字段 例如 recordName 这是记录的 ID 我将使用我正在开发的 硬币收集 App 中的例子 我想存储一个国家的集合 所以我有一个记录类型 来描述我需要为它们 存储什么类型的属性 我正在存储名称和 ISO 代码 并将记录类型命名为 Countries ISO 代码唯一标识一个国家 因此将它们包含在 我的记录类型中是很重要的
我创建了一些 Countries 类型的记录 来存储这些信息及其名称
我也有特定国家货币的记录类型 我想把它们相互联系起来 Coins 记录类型存储 硬币与其国家的关系
记录类型和关系组合起来 形成一个架构 我可以将这些元素的当前状态 视为架构的当前版本 在您开发 App 时 您将发展您的架构 并且在您的 App 的生命周期中 您可能会拥有它的多个版本
虽然我的 App 架构描述了 我要存储在 iCloud 中数据的结构 但我的 App 容器 就是存储数据的位置 容器具有唯一标识符 并与开发团队相关联 使用 CloudKit 时需要牢记两种环境 开发环境是一个安全的地方 可以在不干扰用户的情况下进行更改 这是您应该测试 和开发架构变更的地方 当用户与您的 App 交互时 他们将与生产环境交互 生产环境包含 App 的实时数据 现在我已经回顾了 CloudKit 如何存储数据 下面我将介绍如何配置 CKTool JS 因为 CKTool JS 会与 iCloud 对话 所以您需要收集一些信息 以便它知道如何使用正确的容器 并且您的脚本有权这样做
您需要您的团队 ID 和要使用容器的容器 ID 您需要一个管理令牌才能使用架构 如果您的脚本要访问数据 您还需要一个用户令牌 所有这些值都可以 从 CloudKit Console 获取 您还需要指定脚本 将在哪个环境中运行 是开发环境 还是生产环境 接下来 我将以开发环境为例 每当您配置 CKTool JS 以供使用时 您都需要这些值 对于我的示例 我正在为 Node.js 编写脚本 您可以从 CKTool JS 导入对象和函数来使用它们 在这种情况下 您可以使用 CommonJS 的 require 语句导入这些符号 一旦您收集了配置信息 您将创建对象来保存这些信息 要存储您的身份验证令牌 您需要创建一个对象 来保存您的管理令牌和用户令牌 如果您有用户令牌的话 因为 teamId containerId 和 environment 是传递给 CKTool JS 的公共值 您可以创建一个对象来保存这些值 通过使用 createConfiguration 工厂函数 实例化一个配置对象 告诉 CKTool JS 如何与 iCloud 对话 createConfiguration 是平台特定的 在这种情况下 它将为 Node.js 返回一个适当的配置 因为这是从目标包中导入的函数 然后传递配置对象 和之前声明的安全对象 来初始化 API 对象 API 对象包含 允许您与 iCloud 对话的异步方法 您现在已经完成了 在脚本中使用 CKTool JS 的步骤 让我们了解如何使用 CKTool JS 来管理容器的架构 在我的 App 中 我想存储 诸如 2007 年发行的 美国一角硬币之类的信息 这枚硬币由铜和镍组成 面值是 1/10 美元 在考虑了如何存储这些数据后 我决定将硬币的 成分信息 作为记录存储 与硬币的其他细节分开 因此我将一角硬币的铜百分比 和镍百分比 存储在不同的记录中 我在容器的架构中 确定了两种记录类型 Coins 存储了国家参考 发行年份和面值 还有一个 Components 记录类型 它存储了对它所描述硬币的引用 以及硬币中的材料及其百分比 现在我已经确定了 App 的架构 我可以在 CloudKit 架构语言中 创建一个文本文件来描述它 惯例是为架构文件使用 .ckdb 扩展名
有关 CloudKit 架构语言的更多信息 请参阅文档 "Integrating a Text-Based Schema into Your Workflow"
您为容器创建的架构文件 可以使用 CKTool JS 来应用 在应用新架构之前 通常会重置容器的开发架构 以匹配生产中的架构 您可以使用 resetToProduction 方法执行此操作 您可以通过传递 之前声明的 defaultArgs 对象 来调用此方法 如果您的架构不在生产中 则会删除所有记录类型 否则这会将开发架构 恢复到生产环境的状态 注意这是一个异步调用 所以这个方法返回一个 promise 对象
CKTool JS 具有允许您 导出和导入容器架构的方法 exportSchema 和 importSchema 方法 允许您执行此操作 并且从容器的角度命名 因此您使用 exportSchema 下载 要从容器中导出的架构 然后使用 importSchema 上传要导入到容器中的架构 总之 这些允许您 管理架构的演变
您可以创建一个帮助函数 来将架构应用于容器 首先 从 CKTool JS 中导入 File 对象 然后从 Node.js 中 导入 fs 和 path 模块 现在定义一个异步函数 它将执行以下操作 它将架构文件的内容 读入 Node.js 缓冲区 它创建一个 CKTool JS File 实例以供上传 最后 它使用 importSchema 将文件的内容上传到服务器 请注意 之前声明的 defaultArgs 对象 将传递给 importSchema 现在您可以把它放在一起了 因为用于导入架构的 resetToProduction 和辅助函数是异步的 所以您需要确保它们 以正确的顺序运行 为此 您需要链接承诺 如果发生错误 承诺将拒绝 除了 CKTool JS 的管理能力之外 它还允许您处理数据的读写 CKTool JS 记录中使用的字段值 在发送到服务器之前 会在客户端进行类型和范围检查 如果传入的值不是正确的值类型 或者超出了值的允许范围 则会抛出异常 对于无法在 JavaScript 中 本机表示的大数 可以使用 CKTool JS 类型来替代 例如 要将数字强制转换为 CKTool JS Int64 请使用 toInt64 函数 要将数字强制转换为 Double 浮点值 请使用 toDouble 函数 如果您正在编写 TypeScript 如果不使用这些强制函数 编译器将标记不正确的值类型用法
CKTool JS 记录中的字段值 是使用字段值工厂函数创建的 对于 2007 年发行的硬币 我会将该值传递给 makeRecordFieldValue.int64 工厂函数 以创建包含 Int64 的记录字段值 一般来说 如果工厂函数不能从传入的值 创建记录字段值 它会抛出异常
这里我创建了一个对象 来保存我发送给 处理记录方法的公共值 由于 containerId environment databaseType 和 zoneName 通常是必需的 因此我将它们包含 在此 databaseArgs 对象中 为了查询记录 我使用 queryRecords 方法 为了使这更容易 我创建了一个辅助函数 来查找与其唯一的 3 字符 ISO 代码匹配的国家 在这种情况下 除了包含查询的主体之外 我还传递了 databaseArgs 对象的内容 对于查询对象 我指定了 recordType 值 以及单个 filter 对象 filter 对象描述了一个查询 其中国家的 isoCode3 等于这个函数正在查找的值 如果成功 找到的记录集合 将出现在 response.result.records 属性中 我从这个集合中返回第一个对象
为了将原始值转换为 createRecord 可以使用的字段值 我有一个名为 makeCoinFieldValues 的 辅助函数来执行此操作 对于我想要转换为字段值的 硬币的每个原始属性 我调用相应的记录字段工厂函数 然而 对于 country 字段 我需要创建一个引用 我使用传入的 countryRecordName 从这个硬币记录中 引用相应的国家记录
在这里 我创建了一个辅助函数 它获取硬币记录字段值 并将 createRecord 请求 发送到服务器 在这个函数中 我传递了之前声明的 databaseArgs 的内容和一个主体 正文字典包含记录类型和字段值 如果成功 则返回 response.result.record
在调用辅助函数之前 我需要获取 将从该硬币引用的 country record 我使用前面定义的国家查询功能 然后我通过传递一个字段值字典 来调用 coinCreateRecord 该字典是使用我之前编写的 makeCoinFieldValues 辅助函数 创建的 原始硬币值被传递给该辅助函数 这将异步创建记录并返回新记录
要更新记录 请使用 updateRecord 方法 我创建了一个辅助函数 它使用传递给该辅助函数的字段 更新与记录名称匹配的硬币 然后我使用 databaseArgs 对象的 内容 recordName 和一个包含记录类型和新记录的 字段值的正文调用 updateRecord 如果成功 更新的记录将在 我从帮助函数返回的 response.result.record 属性中
为了更新我之前创建的硬币记录 我调用这个辅助函数 传入它的记录名称和字段值来更新 字段值是使用 makeCoinFieldValues 创建的
要删除记录 我在 API 对象上 调用 async deleteRecord 方法 我传入了 databaseArgs 对象的内容 以及要删除的记录的 recordName 我希望您想要去了解 CKTool JS 可以亲自尝试一下 为您的自动化和工具化目的 配置 CKTool JS 使用 JavaScript 重置 导入您的架构 以及读取和写入您的数据 要在持续集成场景中 使用 CKTool JS 请查看 GitHub 上的 CloudKit 示例 repo 如需更详细的文档 请查看 developer.Apple.com 上的 CKTool JS 感谢您今天加入我的行列 请享受余下的 WWDC22 之旅吧
-
-
6:43 - Create security and default arguments objects
// Create security object and setup default args const { CKEnvironment } = require("@apple/cktool.database"); const security = { "ManagementTokenAuth": "<YOUR_MANAGEMENT_TOKEN>", "UserTokenAuth": "<YOUR_USER_TOKEN>" }; const defaultArgs = { "teamId": "<YOUR_TEAM_ID>", "containerId": "<YOUR_CONTAINER_ID>", "environment": CKEnvironment.DEVELOPMENT };
-
7:17 - Create configuration and API objects
// Create configuration and API objects const { createConfiguration } = require("@apple/cktool.target.nodejs"); const { PromisesApi } = require("@apple/cktool.database"); const configuration = createConfiguration(); const api = new PromisesApi({ "configuration": configuration, "security": security });
-
10:00 - Reset to production and import schema
// Create a function to apply a schema const { File } = require("@apple/cktool.target.nodejs"); const fs = require("fs/promises"); const path = require("path"); const importMySchema = async () => { const schemaPath = "<YOUR_SCHEMA_FILE>.ckdb"; const buffer = await fs.readFile(schemaPath); const file = new File([buffer], schemaPath); await api.importSchema({ ...defaultArgs, "file": file }); } // Chain the calls api.resetToProduction(defaultArgs) .then(() => importMySchema());
-
11:36 - Factory functions
// Create fields with factory functions. const { makeRecordFieldValue } = require("@apple/cktool.database"); const value = makeRecordFieldValue.int64(2007);
-
12:02 - Create database arguments object
// Create a database arguments object. const { CKDatabaseType, CKEnvironment } = require("@apple/cktool.database"); const databaseArgs = { "containerID": "<YOUR_CONTAINER_ID>", "environment": CKEnvironment.DEVELOPMENT, "databaseType": CKDatabaseType.PRIVATE, "zoneName": "_defaultZone" };
-
12:16 - Query for records
// Define helper function for querying records const { CKDBQueryFilterType } = require("@apple/cktool.database"); const countryQueryRecordForCountryCode3 = async (countryCode3) => { const response = await api.queryRecords({ ...databaseArgs, "body": { "query": { "recordType": "Countries", "filters": [{ "fieldName": "isoCode3", "fieldValue": makeRecordFieldValue.string(countryCode3), "type": CKDBQueryFilterType.EQUALS }] } } }); return response.result.records[0]; }
-
12:58 - Create field values
// Define a helper function for creating field values const { makeRecordFieldValue, CKDBRecordReferenceAction } = require("@apple/cktool.database"); const makeCoinFieldValues = ({ countryRecordName, issueYear, nominalValue }) => ({ "country": makeRecordFieldValue.reference({ recordName: countryRecordName, action: CKDBRecordReferenceAction.DELETE_SELF }), "issueYear": makeRecordFieldValue.int64(issueYear), "nominalValue": makeRecordFieldValue.double(nominalValue) });
-
13:26 - Create a record
// Define helper method for creating coins const coinCreateRecord = async (fields) => { const response = await api.createRecord({ ...databaseArgs, "body": { "recordType": "Coins", "fields": fields }, }); return response.result.record; }
-
13:48 - Call record creation helper method
// Call coin creation method with field values const countryRecord = await countryQueryRecordForCountryCode3("USA"); const coinRecord1 = await coinCreateRecord( makeCoinFieldValues({ "countryRecordName": countryRecord.recordName, "issueYear": 2007, "nominalValue": 0.10 }) );
-
14:16 - Define update record helper function
// Define helper method for updating coins. // Note that recordChangeTag is required const coinUpdate = async (recordName, recordChangeTag, fields) => { const response = await api.updateRecord({ ...databaseArgs, "recordName": recordName, "body": { "recordType": "Coins", "recordChangeTag": recordChangeTag, "fields": fields } }); return response.result.record; }
-
14:44 - Update a record with field values
// Call coin updating method with field values. // Note that the recordChangeTag of the record // to update is passed to the coin update function. const countryRecord = await countryQueryRecordForCountryCode3("USA"); const updatedCoinRecord1 = await coinUpdate( coinRecord1.recordName, coinRecord1.recordChangeTag, makeCoinFieldValues({ "countryRecordName": countryRecord.recordName, "issueYear": 2010, "nominalValue": 0.10 }); );
-
14:57 - Delete a record
// Deleting a record await api.deleteRecord({ ...databaseArgs, "recordName": coinRecord1.recordName });
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。