大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 Embedded Swift 实现轻量级开发
Embedded Swift 让你在受限环境中仍能充分利用 Swift 的安全性和表现力。我们将使用现成的 Matter 设备进行展示,带你探索 Embedded Swift 在各种微控制器上的运行效果。了解 Embedded Swift 这个不含运行时且占用空间极小的子集如何为你带来 Swift 的种种好处,并探索众多资源来开启自己的 Embedded Swift 开发之旅。
章节
- 0:00 - Introduction
- 0:25 - Agenda
- 0:46 - Why Embedded Swift
- 2:30 - Showcase
- 2:47 - The plan
- 3:39 - Getting started
- 6:19 - Using Swift's interoperability to control the LED
- 7:12 - Using an ergonomic LED struct
- 10:07 - Adding the Matter protocol
- 13:43 - Using a Swift enum in the event handler
- 16:52 - Demo summary
- 17:34 - How Embedded Swift differs
- 19:48 - Explore more
- 21:32 - Wrap up
资源
- A Vision for Embedded Swift
- Embedded Swift User Manual
- Forum: Programming Languages
- Swift Embedded Example Projects
- Swift Forums Embedded Discussion
- Swift Matter Examples
- Swift MMIO
- Tools used: Neovim
相关视频
WWDC21
-
下载
大家好 欢迎观看本次讲座 我叫 Kuba Mracek 众所周知 Swift 是一种强大的语言 可用于为 Apple 平台和其他平台 构建应用程序 今天 我们将介绍一个 令人兴奋的 Swift 新应用 它将在更小的空间里大展身手 那就是嵌入式设备 首先 我将介绍 Embedded Swift 然后我将演示 如何用它来构建一些实用的东西 我将介绍 Embedded Swift 与 Swift 的区别 Swift 用于编写 桌面应用程序和移动应用程序 最后 我将分享 可供你进一步探索的资源 我们直接开始吧 如今 你可以使用 Swift 来构建许多不同类型的软件 你可以针对移动设备、 台式电脑和服务器进行构建
在本讲座中 我们将讨论 Swift 在新领域的应用 也就是嵌入式设备 嵌入式设备在日常生活中无处不在 智能灯、恒温器、闹钟、 智能风扇、音乐设备、灯带 还有许多常见的小玩意 都内置了可编程微控制器 无论你是业余爱好者 还是真正的开发者 今天我将向各位介绍如何 使用 Swift 为嵌入式设备编程 为此我们推出了 Embedded Swift 这是一种新的编译模式 专门用于受限的嵌入式设备 过去 C 和 C++ 是这个领域的常用语言 但现在 我们支持 在这些地方使用 Swift 嵌入式开发者将能够享受 Swift 的种种好处 比如它的人体工程学设计、 安全功能和易用性
Embedded Swift 当然很适合 对嵌入式设备中的微控制器 进行编程 但它也适用于内核级代码 和其他低级资料库代码 这类代码可能对 不能获得新的依赖项很敏感 Apple 设备中的安全隔区处理器 就使用了 Embedded Swift Swift 的内存安全性 为平台带来了巨大的益处 Embedded Swift 是 Swift 的子集 涵盖你所熟知和喜爱的大部分语言 并且它是一个功能完备的子集 支持值和引用类型、 闭包、可选类型、 错误处理、泛型等 现在让我们通过现场演示 来了解 Embedded Swift
在开始之前 请注意 Embedded Swift 目前处于实验阶段 它的源代码还不稳定 还在持续开发中 它的最佳使用方法是 搭配 swift.org 中的 预览版工具链
在这个演示中 我将构建一个非常简单的 HomeKit 配件的原型 彩色 LED 灯 首先我需要一个有效的 HomeKit 设置 也就是一个 Wi-Fi 网络 以及连接到这个网络的 一些现有 Apple 设备 我将使用一个可编程的嵌入式设备 具体来说就是一块 ESP32C6 开发板 搭载一个 RISC-V 微控制器 它还将连接一个彩色 LED 灯 我将通过 USB 数据线把 Mac 连接到设备 在 Embedded Swift 中 写一个程序 它会实现 HomeKit 配件 然后我会将它刷入设备 然后设备将加入我的 Wi-Fi 和 HomeKit 网络 可以通过任何 Apple 设备上的 “家庭”App 来控制这个设备
让我们开始吧 我使用的是 Neovim 和 CMake 现在这只是一个模板项目 展示了最基本的结构 并让我们的 Embedded Swift 代码正常运行 这个项目使用的是我从 设备供应商获取的第三方 SDK 它是一个用 C 语言编写的 SDK 因为我不想修改这个 SDK 我可以只使用桥接标头 将 SDK 的所有 API 导入 Swift
我需要一些简单的 CMake 逻辑 来构建我的 Swift 代码 代码将构建在供应商的 SDK 和他们的构建系统之上
这还需要几个样板文件: 比如 YAML 文件
CSV 文件
和“sdkconfig”文件
任何基于这个 SDK 构建的项目 都需要这些文件 我已经根据供应商提供的 现有示例进行了设置 并在上面添加了 Swift
现在回到我的 Swift 源代码 我的编辑器集成了 LSP 因此可以显示 我正在使用的函数和类型 的定义和文档
它可以提供丰富的语义自动补全
如果我输入错误的代码
立刻就会看到错误和警告 告诉我哪里出了问题 现在我插入设备
我将通过 USB 对这个设备编程 我的试验板有单独的电池 编程完成之后 我们可以 将设备拔下并继续使用它 但首先 我们要让最基本的 Swift 应用程序在设备上运行起来 我使用的 SDK 提供了相应的工具
我将运行这个便捷的 Python 脚本 它是由供应商提供的 我只需要一条命令就可以 构建、刷入和监控设备
运行之后可以看到固件正在构建 然后上传到设备 接着我们收到设备发回的日志 现在 我们在嵌入式设备上运行的 第一个 Embedded Swift 应用程序有了生命迹象 现在我们将添加代码来实现 一些更有用的功能 Swift 的互操作性使我们能够访问 供应商 SDK 中的所有 API 如果我想控制设备上的 LED 我可以使用 SDK 中 现有的 C API
我们将调用这些 API 并设置以下含义的值 “蓝色”、“100% 饱和度” “80% 亮度”
让我们保存这个版本的代码 并上传到设备
我将运行与上次相同的命令
只需几秒钟 当固件上传完成 并且设备重新启动后 我们就能控制 LED 的颜色和亮度
仅使用 Swift 调用 C API 来完成一切会破坏原本的目的 我们可以这样做 但更好的做法是在这些之上 构建封装层和抽象 以便为应用程序编写清晰、直观 且符合人体工程学的 Swift 代码 在这个版本中 我准备了一个 LED 对象 我们直接查看它的定义
这是我之前编写的一些帮助代码 它将 C API 封装为 一个 Swift 层 它提供了实用的属性 和一些符合人体工程学的类型 例如 enabled 属性是布尔值 brightness 属性是整数 让我们回到 包含主应用程序逻辑的文件 使用 LED 对象 现在我们可以 编写非常简单直观的代码
开始时 我们将颜色设为红色
将亮度设为 80%
像这样的代码非常清晰且易于阅读
让我们再添加一些代码
在一个循环中 我们将等待 1 秒 改变 LED 的状态 如果是打开 LED 则请求一个新的颜色 这通过色相和饱和度值来表达 色相为随机 饱和度为 100% 这些 Embedded Swift 代码 感觉就像是用普通 Swift 写的 大多数语言都是现成的 我将再次上传固件
我们来看看结果如何 程序启动并运行后 设备的 LED 应该会闪烁 随机变化的颜色
太棒了!这正是我们想要的结果 构建 LED 对象层使我们能够 利用 Swift 的强大能力 即借助高级 API 编写清晰 且易于阅读的代码
现在 我们已经了解 Embedded Swift 如何巧妙整合到你的工作流程中 你可以将它与供应商提供的 SDK 搭配使用 你可以使你的 IDE 或文本编辑器 提供完整的自动补全 显示定义和文档 借助 Swift 的互操作性 你可以在 Swift 代码中 直接从 SDK 调用现有的 C API 但将 C API 封装为一个层 通常更有价值 这可以提供直观且符合人体工程学的 API 来实现核心应用程序逻辑
现在我们的基本原型 已经能够正常运行 我们来继续构建 实际的 HomeKit 配件 为此我们将使用“Matter”协议 Matter 是用于构建 智能家居配件的开放标准 2021 年的一个 WWDC 讲座 深入介绍了这个协议 如果你想进一步了解 建议观看这个讲座
在我使用的 SDK 中 Matter 以 C++ API 的形式提供 我们可以再次借助 Swift 的 互操作性来使用这个功能 它将免费提供所有的基础架构组件 如设备发现和调试
一旦我们有了实现 Matter 协议的设备 它将自动在 HomeKit 中运行 因为它原生支持 Matter 配件 我们重新开始创建一个 不实现任何功能的空应用程序 要使用 Matter 我们需要稍微了解一下 它的数据模型和术语 这是一个粗略的任务列表 它们是为实现 Matter 设备 而必须完成的任务 我们需要创建一个根节点 它代表整个 Matter 配件 然后我们需要一个端点 在本例中是彩色 LED 灯 它也将是有状态的对象 例如颜色和亮度 并且可以接收命令 例如打开或关闭指示灯
然后我们将端点连接到节点 并将节点连接到“应用程序”对象 我已经围绕 C++ Matter API 编写了一个简单的封装层 它位于这个“Matter”子目录中
我们也用这种同样的方法来 编写好用的 API 来与 LED 搭配使用 利用它 我们可以轻松地 填充顶层逻辑
首先我们创建根节点
然后创建并配置端点
这是我们的彩色 LED 灯 注意它有一个 eventHandler 闭包 每当 HomeKit 事件发送到这个端点 就会触发这个闭包 闭包是一种非常自然的回调机制 我们不必处理不安全的函数指针 或无类型的上下文变量 即 C 语言中常见的回调解决方案 接下来 我们注册端点 最后 设置并启动 Matter 应用程序
目前 逻辑只是输出所有事件 但我们现在已经构建了 有效的 Matter 应用程序 让我们把这个应用程序刷入设备
启动需要一些时间 正常来说 你需要 完成新配件的设置流程 我之前已完成这个流程 并在我的 HomeKit 网络中 注册了这个设备 所以它已经知道 要加入哪个 Wi-Fi 网络 加入 Wi-Fi 网络后 它将出现在 我的 Mac 以及 其他设备上的“家庭”App 中 并显示为“Matter 配件”
它显示为智能灯 我可以在 Mac 上的 “家庭”App 中控制它 我可以打开和关闭智能灯 这个时候 我们会在 设备日志中收到 与这些命令相关的事件
到目前为止 我们只是 记录了传入的事件 现在来让它们做点什么 将它们连接到 我们之前使用的 LED 对象 在事件处理程序内 我们需要 针对设置的不同属性做出反应 由于属性是 Swift 枚举 我们可以使用 switch 语句
自动补全会告诉我们 必须处理哪些情况
让我们为不同的属性填充代码 根据收到的事件 如果我们 想要打开或关闭智能灯 将需要设置 enabled 属性 或者将设置 brightness 属性 并需要适当调整属性值 我们可以通过同样的方式 处理颜色变化 设置新的色相 新的饱和度或新的色温 我们需要的就是这些 让我们将这个程序 刷入设备并进行测试
在程序正在启动时 我想指出 Swift 枚举十分有用 且符合人体工程学
在最简单的情况下 枚举只代表一组选项中的一个 例如属性的 .onOff case 或 .levelControl case
但它们也可能有关联值 例如 .colorControl case 具有有效负载 多亏 Swift 的模式匹配 我不需要第二个嵌套的 switch 语句 我只需要将枚举 case 与有效负载进行匹配
我还使用枚举来表示 color 属性
可以是色相加饱和度 或是色温
这些 case 甚至有不同的 有效负载类型 前者需要两个整数作为有效负载
后者只需要一个整数 这一切使得 Swift 枚举 具有强大的功能和表达能力 使我们能够编写简单、简洁、 易读的逻辑
现在设备已经启动 我可以拔掉 USB 连接线 通过电池来供电
我们可以从“家庭”App 无线控制这个设备 将智能灯打开
然后关闭
我们来看看能否更改亮度
或者选择其他色温
或者完全自定义颜色 我们来试试不同的颜色
太棒了 我们的智能灯原型 表现得很好 我们成功地使用 Embedded Swift 构建了一个支持 HomeKit 的智能灯 如有需要 你可以在 GitHub 上获取 这个演示项目和设置说明
我们已经了解如何利用 Swift 的互操作性能力 来快速设置和运行 支持 HomeKit 的设备的基本原型 不需要在 Swift 中重新实现 整个 Matter 协议 我们可以仅使用 Swift 中现有的实现 Swift 鼓励简洁直观的 代码设计和实现 并且可读性和安全性 优于 C 和 C++ 例如使用闭包作为 符合人体工程学的回调解决方案 在演示中我们看到 Embedded Swift 与普通 Swift 很相似 它确实具有 Swift 的 大部分语言特性 但也有一些不同
嵌入式环境通常非常受限 需要体积小且内容简单的 程序二进制文件 内存、存储和 CPU 性能 通常非常有限 因此 Embedded Swift 禁用 某些功能以满足这些要求 例如 让我们考虑运行时反射 的工作原理 要检查某个类型的子项 它需要访问类型的元数据记录 这包括字段的名称、 偏移和类型 它们又会引用 其他元数据记录 以此类推 这些记录会累积 导致代码大小超出 嵌入式设备的承受能力
为避免这个问题 运行时反射使用 Mirror API 这在 Embedded Swift 中是禁用的 仅在完整版 Swift 中提供 出于相同的原因 为避免在运行时需要元数据 也不允许使用元类型和“any”类型 不过不用担心 绝大部分 Swift 语言特性 在 Embedded Swift 中都支持
Embedded Swift 是严格意义上的 完整版 Swift 的子集 而不是变体或方言 因此 Embedded Swift 和 完整版 Swift 的行为 不会有任何差异 所有能在 Embedded Swift 中运行的 代码也能在完整版 Swift 中运行
当你尝试使用 Embedded Swift 中 不支持的功能时 编译器会提示你 在这个例子中 我尝试使用 any 类型
为避免这个问题 我可以 将 any Countable 替换为泛型 在这段代码中 只需将 any Countable 改为 some Countable 就能将这个函数转变为泛型函数 Embedded Swift 全面支持泛型 因为编译器可以对泛型函数进行特化 从而使代码不需要广泛的运行时支持 或类型元数据 Embedded Swift 还有很多值得探索的地方 作为 Swift Evolution 过程的一部分 一份 Embedded Swift 愿景文档 已发布并得到大家接受 这份文档说明了 Embedded Swift 的高层次设计、 要求以及方法 很好地介绍了编译模式和语言子集 如果你要试用 Embedded Swift 我推荐你阅读 “Embedded Swift -- 用户手册” 它介绍了入门操作、 你应该和不应期待的结果 以及在与供应商的 SDK 和 构建系统交互时 可能需要了解的细节 例如 要使用的编译器标志 以及需要满足的依赖项
我们在 GitHub 上发布了 一组示例项目 它们以 Embedded Swift 语言编写 涵盖了一系列具有 ARM 或 RISC-V 微控制器 的嵌入式设备 其中包括热门的嵌入式开发板 以及 Playdate 游戏机等其他设备 这些示例还展示了如何使用各种 构建系统和集成选项 它们可让你大致了解 Embedded Swift 的功能 还可以用作你自己想法的模板 当编写嵌入式设备所运行的代码时 你经常需要与底层硬件寄存器交互 为了帮助你 我们发布了“Swift MMIO” 这是一个提供 API 的资料库 可在内存映射寄存器上实现安全、 结构化且符合人体工程学的操作 最后 Swift 论坛新增了 “Embedded”子类别 你可以在这里发布试验、 提问和进行讨论
我们了解了如何使用新的编译模式 Embedded Swift 对嵌入式设备进行编程 它目前为预览版 最好与 swift.org 上的 工具链搭配使用 目前支持 32 位和 64 位的 ARM 和 RISC-V 芯片 但 Embedded Swift 并非特定于硬件并且很容易 与新的指令集集成 赶快尝试使用 Embedded Swift 构建有趣的电子产品项目 并在 Swift 论坛上 分享你的经验和反馈吧 感谢观看 祝你在 WWDC 度过美妙的时光
-
-
3:50 - Empty Embedded Swift application
@_cdecl("app_main") func app_main() { print("🏎️ Hello, Embedded Swift!") }
-
6:48 - Turning on LED to blue color
@_cdecl("app_main") func app_main() { print("🏎️ Hello, Embedded Swift!") var config = led_driver_get_config() let handle = led_driver_init(&config) led_driver_set_hue(handle, 240) // blue led_driver_set_saturation(handle, 100) // 100% led_driver_set_brightness(handle, 80) // 80% led_driver_set_power(handle, true) }
-
8:32 - Using an LED object
let led = LED() @_cdecl("app_main") func app_main() { print("🏎️ Hello, Embedded Swift!") led.color = .red led.brightness = 80 while true { sleep(1) led.enabled = !led.enabled if led.enabled { led.color = .hueSaturation(Int.random(in: 0 ..< 360), 100) } } }
-
12:44 - Matter application controlling an LED light
let led = LED() @_cdecl("app_main") func app_main() { print("🏎️ Hello, Embedded Swift!") // (1) create a Matter root node let rootNode = Matter.Node() rootNode.identifyHandler = { print("identify") } // (2) create a "light" endpoint, configure it let lightEndpoint = Matter.ExtendedColorLight(node: rootNode) lightEndpoint.configuration = .default lightEndpoint.eventHandler = { event in print("lightEndpoint.eventHandler:") print(event.attribute) print(event.value) switch event.attribute { case .onOff: led.enabled = (event.value == 1) case .levelControl: led.brightness = Int(Float(event.value) / 255.0 * 100.0) case .colorControl(.currentHue): let newHue = Int(Float(event.value) / 255.0 * 360.0) led.color = .hueSaturation(newHue, led.color.saturation) case .colorControl(.currentSaturation): let newSaturation = Int(Float(event.value) / 255.0 * 100.0) led.color = .hueSaturation(led.color.hue, newSaturation) case .colorControl(.colorTemperatureMireds): let kelvins = 1_000_000 / event.value led.color = .temperature(kelvins) default: break } } // (3) add the endpoint to the node rootNode.addEndpoint(lightEndpoint) // (4) provide the node to a Matter application, start the application let app = Matter.Application() app.eventHandler = { event in print(event.type) } app.rootNode = rootNode app.start() }
-
18:03 - Reflection example
// Reflection needs metadata records let mirror = Mirror(reflecting: s) mirror.children.forEach { … } struct MyStruct { var count: Int var name: String }
-
18:57 - Unavailable features will produce errors
// Unavailable features will produce errors protocol Countable { var count: Int { get } } func count(countable: any Countable) { print(countable.count) }
-
19:24 - Prefer generics over “any” types
// Prefer generics over “any” types protocol Countable { var count: Int { get } } func count(countable: some Countable) { print(countable.count) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。