Mix Swift and C++
Swift & UI 进阶 20m

混合使用 Swift 和 C++

Mix Swift and C++

2023年6月5日

在 Apple 官方观看视频

一句话判断

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” 可以获得更底层的实现细节。
WWDC 2023