混合使用 Swift 和 C++
Mix Swift and C++
2023年6月5日
一句话判断
Xcode 15 终于让 Swift 直接调用 C++ 代码——不需要 Objective-C 桥接层,不需要手动封装,编译器自动完成双向互操作。如果你的项目有大量 C++ 代码,这是迁移 Swift 的最大推力。
这场 Session 讲了什么
Session 介绍了 Xcode 15 中 Swift 和 C++ 双向互操作的能力。分两部分:
基础互操作:
- 在 Xcode 项目中启用 C++ 互操作性:Build Settings 中将 “C++ and Objective-C Interoperability” 从 “C and Objective-C” 切换到 “C++ and Objective-C++”。
- Swift 文件可以直接 import C++ 框架,无需 bridging header。
- 在 Swift 中调用 C++ 方法就像调用 Swift 方法一样。
- 在 C++/Objective-C++ 中通过 Swift 生成的头文件调用 Swift 代码。
- 零开销:C++ 和 Swift 之间的调用没有桥接层开销。
Demo 展示了一个照片编辑 App:C++ 写的图像处理框架 + Objective-C++ 写的 UI 层 + 新增的 SwiftUI PhotoPicker。SwiftUI 视图直接调用 C++ 框架的 loadImage 方法,Objective-C++ ViewController 直接使用 SwiftUI 视图。
API 微调(Annotations):
swift_name:重命名 C++ 函数在 Swift 中的显示名称,添加参数标签。swift_attr:声明引用语义,让 C++ 类型在 Swift 中表现为 class 而非 struct。swift_private:隐藏不需要暴露给 Swift 的 API。- 将 getter/setter 对导入为 Swift 计算属性。
编译器支持的互操作范围:
- C++ 标准库容器自动导入为 Swift Collection。
- 函数模板和类模板特化。
- shared_ptr 等用户自定义内存管理类型。
- Swift 的 struct、class、方法、泛型类型(包括 Array)都可以暴露给 C++。
值得深挖的点
零开销互操作的设计选择值得理解。Swift 编译器直接理解 C++ 类型系统,不需要通过 Objective-C runtime 或其他中间层。调用 C++ 函数就是直接的函数调用,没有 boxing/unboxing。这对性能敏感的场景(游戏引擎、图像处理、科学计算)至关重要。
C++ vector 在 Swift 中的值语义是个有意思的适配。C++ 的 vector 默认导入为 Swift struct,遵循 Swift 的值语义——赋值和传参会触发复制。但这和 C++ 中的 move 语义不同。如果你想让 C++ 类型在 Swift 中表现为引用语义,需要用 swift_attr 注解声明。
Swift 生成的 C++ 头文件是双向互操作的关键。Swift 编译器会为所有 public API 生成一个 C++ 头文件,C++ 代码 import 这个头文件就能直接使用 Swift 类型。包括构造 SwiftUI 视图、调用 Swift 方法、访问 Swift 属性——都有代码补全和跳转定义支持。
代码片段
在 Swift 中直接调用 C++ 框架:
// 无需 bridging header,直接 import
import ImageKit // 这是你的 C++ 框架
struct PhotoPickerView: View {
var selectedImages: [PhotosPickerItem]
var body: some View {
// 直接调用 C++ 方法——就像调用 Swift 方法一样
let engine = CxxImageEngine.shared
for image in selectedImages {
engine.loadImage(image) // C++ 方法!
}
}
}
在 Objective-C++ 中使用 Swift 视图:
// ViewController.mm (Objective-C++)
#import "MyApp-Swift.h" // Swift 自动生成的头文件
@implementation ViewController
- (void)presentPhotoPicker {
// 直接构造 SwiftUI 视图
PhotoPickerView *picker = [[PhotoPickerView alloc] init];
[self presentViewController:picker animated:YES completion:nil];
}
@end
使用注解优化 C++ API 在 Swift 中的表现:
// 重命名函数并添加 Swift 风格的参数标签
void loadImageAt(const std::string& path)
__attribute__((swift_name("loadImage(at:)")));
// 将 C++ 类导入为 Swift class(引用语义)而非 struct(值语义)
class ImageEngine {
public:
// 导入为 Swift 计算属性而非 getter/setter 方法
const std::string& getName() const
__attribute__((swift_name("getter:name()")));
void setName(const std::string& name)
__attribute__((swift_name("setter:name(_:)")));
};
// 隐藏不需要暴露给 Swift 的内部 API
void internalHelper() __attribute__((swift_private));
最佳实践
- 先从新功能开始用 Swift,不要试图一次性重写整个 C++ 代码库。SwiftUI 视图 + C++ 业务逻辑的组合是最自然的切入点。
- 对于已有 Objective-C++ 代码的项目,新增 Swift 文件不需要修改任何现有代码,只需要在 Build Settings 中启用 C++ 互操作性。
- 用
swift_name注解为 C++ API 添加 Swift 风格的参数标签,让调用方感觉像原生 Swift API。 - 具有引用语义的 C++ 类型用
swift_attr声明,避免 Swift 按值语义复制导致的意外行为。 - 利用 Xcode 的代码补全和跳转定义——C++ 互操作完全集成在 IDE 中,不需要额外工具。
- 性能关键的路径不需要担心互操作开销——直接调用没有中间层。
还有什么值得关注
- C++ 互操作支持 shared_ptr 的自动管理——Swift 编译器理解 retain/release 操作并据此优化。
- Swift 的泛型类型(包括 Array)可以暴露给 C++,C++ 代码可以直接操作 Swift Array。
- Resilient types(可随时间演化的类型)也支持暴露给 C++,这对库的版本兼容性很重要。
- 如果你的 C++ 代码之前需要 Objective-C 桥接层才能被 Swift 使用,现在可以删除这个中间层了。
- 搭配 Session “Meet C++ interop” 可以获得更底层的实现细节。