大多数浏览器和
Developer App 均支持流媒体播放。
-
融合 Swift 和 C++
了解如何在你的 C++ 和 Objective-C++ 项目中使用 Swift,使你的代码更安全、快速和易于开发。我们将向你展示如何使用 C++ 和 Swift API 来逐步将 Swift 融入你的 App。
章节
- 0:40 - Basics of interoperability
- 2:22 - Adding Swift to a C++ codebase
- 4:10 - Calling a C++ method in Swift
- 4:50 - Calling a Swift method in C++
- 7:32 - Improving how C++ APIs are imported
- 12:15 - Foreign reference types
资源
相关视频
WWDC23
-
下载
♪ ♪
Zoe:你好 我是 Zoe 是 Apple Compiler 团队的工程师 今天 我将介绍 Xcode 15 的新功能 Swift 和 C++ 互操作 使你可同时使用 Swift 和 C++ 本讲将分为两个部分 首先 我将解释互操作的基础知识 然后我将向你展示如何 自然和安全地在 Swift 中 应用你的 C++ API Swift 推出时 世界上已有许多用 Objective-C 编写的大型 App 和代码库 Swift 能够利用这些现有的代码 并逐步被这些代码库采用 这是至关重要的一点 今天 Swift 将这种互操作 提升到了一个新的水平 使你可在更多的地方采用 Swift
如果你有一个大型的 C++ 代码库 你现在可利用双向互操作 逐步采用 Swift 如你的 App 需要访问一个 C++ 库 你不再需要编写 Objective-C 桥接层 让我们通过查看一个示例 App 来看看在 C++ 代码库中 采用 Swift 有多容易
我正在开发一个照片编辑 App 它允许我从相机胶卷中 选择一张图片 进行反色、调节亮度等操作
在进入代码之前 让我们先看看我的 App 的结构
该 App 可分为两个部分 图像处理框架和用户界面代码 我的 App 基于图像处理框架 该框架是用 C++ 编写的 我希望我的用户界面层能够轻松地 与我的 C++ 框架通信 所以我使用了 Objective-C++ 来实现大部分用户界面 比如使用 ViewController 现在 我希望我的 App 用户 能够从他们的相机胶卷中 选择一些照片进行编辑 我听说 SwiftUI 有一个新的 PhotoPicker 视图可轻松实现这点 所以我想 开始在我的 App 中采用 Swift 幸运的是 从 Xcode 15 开始 我可轻松地在我的 Objective-C++ 代码库中采用 Swift 并仍可访问所有我的 C++ API 让我们从为项目 添加一个 Swift 文件开始吧 因为我使用了一个 C++ 框架 Xcode 会自动导入我的 C++ API 所以我不需要桥接头文件
然后 我需要在项目编译设置中 启用 C++ 互操作 如你所知 Swift 已可调用 C 和 Objective-C API 所以当前编译设置为 C 和 Objective-C 模式 但我可将其更改为 C++
现在编译设置就变为 C++ 和 Objective-C++ 模式 我可以直接调用我的 C++ 图像工具包框架中的 API 回到我的 Swift 文件 我可以 像导入其他 Swift 模块一样 导入该框架 并且我可点击模块名以查看其内容 现在 这些代码 可能看起来像 Swift API 但这些代码实际上 来自我的 C++ ImageKit 库; 这只是 Swift 编译器显示代码的方式 让我们看一下 我今天要使用的一些 API
从最底层开始 你可以看到一个类型 为 CxxImageEngine 的静态成员 这目前被导入为一个不安全指针 我们稍后会详细介绍 CxxImageEngine 还有一些其他成员 即 loadImage 和 getImages 我很快就会用到它们 现在我将放入 我的照片选择器的所有 UI 这样我就可以 专注于与 C++ 交互的两种方法
我可获取共享的 CxxImageEngine 并在每个选定图像上 调用 loadImage 将选定图像加载到引擎中 哇 用 Swift 调用一个 C++ 方法真是超级简单 现在 我的 SwiftUI 视图已完成 我想在我的 Objective-C++ ViewController 中使用该视图
为此 我需要公用我的结构体 以便我的 Objective-C++ 代码可访问该结构体
太棒了!我的 所有 Swift 代码都已成功构建 现在我可转到 ViewController 文件 并导入 Swift 生成的头文件
此头文件 包含所有我的公共 Swift API 现在我已导入了生成的头文件 可以开始 在 C++ 中调用我的 Swift 代码 首先 我将构造 SwiftUI 视图 然后 我可以调用 present 方法
Xcode 代码补全功能 会自动帮我填写代码
让我们在设备上测试一下
编译和运行 App 后 你可以看到我们的新 SwiftUI 视图 直接导入到了 我的 Objective-C++ App 中
这是一个真正双向互操作的示例 我能够无缝地 在 Swift 中使用 C++ 类型和函数 反之亦然 在 C++ 中 我能够构造和使用 SwiftUI 视图 其视图的主体 则回调到我的 C++ 框架中 所有的集成 均由 Swift 编译器自动完成 所以我不需要编写桥接层 所有的 API 都是直接的、原生的 与大多数互操作语言不同 在 Swift 中调用 C++ API 无任何额外开销 反之亦然 我今天演示的是一个很小的 App 但 Swift 编译器可支持 大型和复杂代码库的互操作
Swift 可导入大多数 C++ 集合 包括从标准库和从其他地方 Swift 可处理 函数模板和类模板特化 支持使用共享指针 和类似用户自定义类型 来管理内存 Swift 可高水平 理解这些导入的 API 比如 它了解 共享指针的保留和释放操作 并且可利用这种高级知识 来应用一整套强大的优化方法
另一方面 你可将 大多数 Swift API 暴露给 C++ 比如结构体、类、方法和其他成员 你甚至可以暴露像 Array 这样的泛型类型 和随时间演变的弹性类型 C++ 互操作 在 Xcode 中也得到了完全支持 这两种语言均支持代码补全、 跳转到定义和调试器支持功能 这些只是一些 C++ 互操作所支持的 API Swift 编译器支持 使用所有这些 API 和更多 API 的 大型代码库之间的互操作 提升了跨语言的聚合体验 并允许你在更多地方采用 Swift 现在我们已掌握互操作的基础知识 让我们深入探讨这个特性 探索一些方法 在 Swift 中 自然和安全地应用 C++ API Swift 编译器能够 自动导入大多数 C++ API 并将它们表示为安全的 Swift API 比如 默认情况下 C++ 类型 将被导入为 Swift 结构体 C++ 运算符 将被映射为类似的 Swift 运算符 容器则被自动导入为集合 但编译器 也允许你微调 API 导入方式 并暴露感觉更自然的 API 为此 你可使用注释 向编译器 提供你的 API 的更多相关信息
比如 一个函数或方法 可能使用 C++ 命名惯例 而该命名 在 Swift 中感觉并不自然 在此情况下 你可使用注释重命名导入的函数 添加参数标签 或将取值函数和 设值函数导入为计算属性
注释还可帮助解释高级模式 如引用语义 并允许你 将某些类型作为 Swift 类导入
或在 Swift 认为 API 不安全 但实际是安全的时候 对其进行纠正
这些注释是个强有力的方法 可向你传递 Swift 正在导入的 API 的相关信息 让我们来确认一些 我在示例 App 中使用的不同 API 并探索如何使用这些注释 来帮助 Swift 以让人感觉安全 和直观的方式导入我的 API
现在我已完成了照片选择器 还想添加一个保存按钮 将编辑后的照片保存回我的照片库
回到 Swift 我可以再次查看导入的所有 API
首先 我需要收集要保存的照片 我可以使用 getImages 函数来完成该任务
getImages 函数 返回一个 C++ 向量 在调用此方法之前 让我们了解一下 向量在 Swift 中的操作细节 Swift 类型分为两类: 值类型和引用类型 在 Swift 中 结构体表示值类型 类则表示引用类型
默认情况下 C++ 类型 将作为值类型导入 Swift
因此 Swift 将向量导入为值类型 行为类似于 Swift 结构体 向量和其他 Swift 结构体之间唯一的区别是 Swift 会使用该类型的特殊成员 比如复制构造函数 来管理生命周期 这些复制构造函数常执行深度复制 与只在修改时复制的 Swift Array 不同 当 Swift 复制向量时 也会复制所有元素
现在我有了一组图像向量 我可以在 for 循环中 迭代向量以获取每个图像 将图像转换回 uiImage 并将图像保存到我的照片库中
这个for 循环可工作 是因为向量有 begin 和 end 方法 所以 Swift 自动将其导入为集合 与集合自动实现一致 使得向量可以很容易地 转换为 Swift 数组 并可访问 map 和 filter 等方法 为了安全起见 重要的是 使用这些 Swift 集合 API 而不要使用不适用于 Swift 安全模型的 C++ 迭代器 API
使用这些 C++ 迭代器 很容易引入错误 比如生命周期问题或无效内存访问 另一方面 Swift 集合 API 是完全安全的 即使当其在 C++ 集合上操作时也是完全安全的
Swift 编译器会 将不安全的 C++ API 标记为不可用 并建议更安全的替代方案 来帮助指导你 使用这些更安全的 API 让我们回到我的 Swift App 有些事一直在困扰我 每次我使用 C++ ImageEngine 时 都会被提醒这是一个不安全的指针 实际上 该类型在 Swift 和 C++ 中 都是作为指针使用的 这是因为该类型 具有所谓的“引用语义” 这意味着该类型应该具有对象标识 并且副本不应有不同的值 而应该共享对同一内存的引用 正如我之前提到的 Swift 类型分为两类: 值类型和引用类型 Objective-C 也明确了 值类型和引用类型之间的区别 这使得将 Objective-C 类型 映射到结构体和类变得容易 C++ 中 哪些类型属于哪个类别不像 Swift 和 Objective-C 那样清晰 不同于 Swift 和 Objective-C C++ 的值类型和引用类型之间 并没有那么清晰的区别
因此 默认情况下 编译器将所有内容作为值类型导入 但是 Swift 还可 向 C++ 代码添加注释 将某些内容导入为引用或类的类型
我可以使用 SWIFT_SHARED_REFERENCE 属性 将 CxxImageEngine 映射到 Swift 类中 该属性意味着 Swift 将强制将类型 始终作为指针或引用进行传参 并将简单地用类型 而非 Swift 中的不安全指针 来表示这种间接调用
为保障你的代码安全 Swift 将根据需要保留和释放引用 来自动管理引用的生命周期 为了启用这种引用计数 你需要 为 Swift 提供保留和释放函数 让我们深入了解 C++ ImageKit 头文件
我可以导入 swift/bridging 来访问 SWIFT_SHARED_REFERENCE 等注释 现在我可以将此注释应用于类型 指定 Swift 可调用的保留和释放函数 太棒了! 现在有一些 Swift 编译器错误 告知我 我不再需要解引用一个指针
我还可做最后一件事 让这个 C++ 的API 完全适应 Swift 在此 for 循环中 我正在调用 getImages 像这样定义取值函数和设值函数 在 C++ 中是一个相当常见的模式 但这在 Swift 中感觉不是很自然 为了使其在 Swift 中显得更原生 我可以使用 swift/bridging 中的另一个注释 SWIFT_COMPUTED_PROPERTY 属性 可应用于取值函数和设值函数 将这个对子 映射到 Swift 计算属性中 让我们再次回到我们的 C++ 头文件 以应用此注释
现在我可以在定义上点击右键 选择我的 Swift 调用情况 来查找我的 getImages 方法的调用情况 现在我可以将其 重命名为简单的“images”
漂亮! 现在让我们 最后测试一次我们的 App
我可以选择一些照片 并将它们保存回我的相机胶卷
太棒了! 在本讲中 我只使用了两个注释 来改进我的 API 导入方式 但是在你的 C++ 头文件中 还有许多其他注释可供使用 你只需导入 swift/bridging 即可访问这些注释
要在 Xcode 15 启用 C++ 互操作 请将 C++ 和 Objective-C 互操作模式 从 C 和 Objective-C 更改为 C++ 和 Objective-C++ 所有 Apple 平台 以及 Linux 和 Windows 均支持 Swift 和 C++ 互操作 C++ 是一种庞大而复杂的语言 我们希望通过你的反馈 来改进导入 C++ API 和暴露 Swift API 的方式 当我们改变 C++ API 导入方式时 我们将创建一个新版本的互操作 这意味着 你可以选择何时使用这些新功能 从而使你可以自信地 从今天开始在开发中使用 C++ API
如你注意到任何问题或有任何建议 我们很乐意听取你的意见 请使用反馈助理告诉我们 C++ 互操作 是由 Swift 编译器工作组 在完全开源代码的情况下设计的 该工作组由来自十多个公司和学校的 工程师和学生组成 工作组制定了两个文件 定义了 Swift 和 C++ 互操作的未来愿景 并将指导这一功能的逐步发展
你可加入工作组并参与论坛谈论 只需前往 swift.org 即可
在 Swift 5.9 中 你可自动且安全地 使用 C++ API 无需额外开销 你可将新的 Swift 代码 暴露给 C++ 来逐步采用 Swift 你可为编译器提供更多信息 来改进和微调导入的 API
感谢你的观看 祝你在所有 C++ 代码库中愉快采用 Swift
-
-
4:10 - Calling a C++ method from Swift
func loadImage(_ image: UIImage) { // Load an image into the shared C++ class. CxxImageEngine.shared.pointee.loadImage(image) }
-
4:20 - Import a C++ framework
import CxxImageKit
-
4:45 - Import the Generated Header
#import "SampleApp-Swift.h"
-
4:57 - Calling a Swift method in C++
- (IBAction)openPhotoLibrary:(UIButton *)sender { // Construct SwiftUI view SampleApp::ImagePicker::init().present(self); }
-
8:22 - Using the SWIFT_COMPUTED_PROPERTY attribute
int getValue() const SWIFT_COMPUTED_PROPERTY; void setValue(int newValue);
-
8:42 - Using the SWIFT_SHARED_REFERENCE attribute
struct SWIFT_SHARED_REFERENCE(retain, release) CxxReferenceType;
-
8:52 - Using the SWIFT_RETURNS_INDEPENDENT_VALUE attribute
SWIFT_RETURNS_INDEPENDENT_VALUE std::string_view networkName() const;
-
10:45 - Using a for-loop to iterate over a C++ std::vector in Swift
// Get every image out of the shared C++ class. for image in CxxImageEngine.shared.pointee.getImages() { let uiImage = CxxImageEngine.shared.pointee.uiImageFrom(image) UIImageWriteToSavedPhotosAlbum(uiImage, nil, nil, nil) }
-
13:54 - Import swift/bridging
#import <swift/bridging>
-
14:01 - Applying the SWIFT_SHARED_REFERENCE attribute to CxxImageEngine
struct SWIFT_SHARED_REFERENCE(IKRetain, IKRelease) CxxImageEngine { // ... };
-
14:53 - Applying the SWIFT_COMPUTED_PROPERTY attribute to getImages
/// \returns all images that have been loaded into the engine. Includes any modifications that were /// applied to the images. SWIFT_COMPUTED_PROPERTY inline std::vector<Image *_Nonnull> getImages() const;
-
15:06 - Updated for-loop using the "images" computed property
// Get every image out of the shared C++ class. for image in CxxImageEngine.shared.pointee.images { let uiImage = CxxImageEngine.shared.pointee.uiImageFrom(image) UIImageWriteToSavedPhotosAlbum(uiImage, nil, nil, nil) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。