Swift 和 C++ 混合使用

目录

简介

C++ 互操作性是 Swift 5.9 中的一项新功能。各种各样的 C++ API 可以直接从 Swift 调用,并且可以选择性地从 C++ 中使用 Swift API。

本文档是描述如何混合使用 Swift 和 C++ 的参考指南。它描述了 C++ API 如何导入到 Swift 中,并提供了示例,展示了如何在 Swift 中使用各种 C++ API。它还描述了如何向 C++ 公开 Swift API,并提供了示例,展示了如何从 C++ 中使用公开的 Swift API。


C++ 互操作性是 Swift 的一项积极发展的功能。它目前支持语言功能子集之间的互操作。 状态页面 概述了当前支持的互操作性功能,并列出了 现有约束

随着 Swift 社区从 Swift 和 C++ 混合代码库中 C++ 互操作性的实际应用中收集反馈,未来版本的 Swift 可能会更改 Swift 和 C++ 的互操作方式。请在 Swift 论坛 上提供您的反馈,或通过在 GitHub 上提交 issue。 未来对 C++ 互操作性的设计或功能的更改 默认情况下 不会破坏现有代码库中的代码。

概览

本节概述了如何混合使用 Swift 和 C++。您可以启用 C++ 互操作性来开始使用。然后您需要了解 Swift 如何 导入 C++ 头文件,以及导入的 C++ 类型和函数如何由 Swift 编译器表示。之后,您需要查看后续章节,了解 如何在 Swift 中使用 导入的 C++ API。您还应该了解如何将 Swift API 公开 到 C++ 代码库的其余部分。如果您有兴趣从 C++ 中使用 Swift API,您肯定需要查看后续章节,了解 如何在 C++ 中使用 公开的 Swift API。

启用 C++ 互操作性

默认情况下,Swift 代码可以与 C 和 Objective-C API 互操作。如果您想从 Swift 中使用 C++ API,或向 C++ 公开 Swift API,则必须启用与 C++ 的互操作性。

以下指南描述了在使用特定构建系统或 IDE 时如何启用 C++ 互操作性

其他构建系统可以通过将所需的标志传递给 Swift 编译器来启用 C++ 互操作性

将 C++ 导入到 Swift 中

头文件通常用于描述 C++ 库的公共接口。它们包含类型和模板定义,以及函数和方法的声明,这些函数和方法的主体通常放在实现文件中,然后由 C++ 编译器编译。

Swift 编译器嵌入了 Clang 编译器。这允许 Swift 使用 Clang 模块 导入 C++ 头文件。与基于预处理器模型(使用 #include 指令直接包含头文件内容)相比,Clang 模块提供了更强大、更高效的 C++ 头文件语义模型。

Swift 目前无法导入 C++20 语言标准中引入的 C++ 模块

创建 Clang 模块

为了让 Swift 导入 Clang 模块,它需要找到一个 module.modulemap 文件,该文件描述了 C++ 头的集合如何映射到 Clang 模块。

一些 IDE 和构建系统可以为 C++ 构建目标自动生成模块映射文件。当 Swift Package Manager 在 C++ 目标中 找到 umbrella 头文件 时,会自动生成模块映射文件。Xcode 会为框架目标自动生成模块映射文件,模块映射文件引用框架的公共头文件。在其他情况下,您可能需要手动创建模块映射。

手动创建模块映射的推荐方法是列出您希望提供给 Swift 的特定 C++ 目标中的所有头文件。例如,假设我们要为 C++ 库 forestLib 创建模块映射。该库有两个头文件:forest.htree.h。在这种情况下,我们可以遵循推荐的方法,创建一个包含两个 header 指令的模块映射

module forestLib {
    header "forest.h"
    header "tree.h"

    export *
}

export * 指令是模块映射的另一个推荐添加项。它确保导入到 forestLib 模块中的 Clang 模块中的类型对 Swift 也可见。

模块映射文件应放在它引用的头文件旁边。例如,在 forestLib 库中,模块映射将放入 include 目录

forestLib
├── include
│   ├── forest.h
│   ├── tree.h
│   └── module.modulemap [NEW]
├── forest.cpp
└── tree.cpp

现在 forestLib 有了一个模块映射,当启用 C++ 互操作性时,Swift 可以导入它。为了让 Swift 找到 forestLib 模块,构建系统必须在调用 Swift 编译器时传递指向 forestLib/include 的导入路径标志 (-I)。

有关模块映射文件的语法和语义的更多信息,请参阅 Clang 的 模块映射语言文档

使用导入的 C++ API

一旦导入 Clang 模块,Swift 编译器就会使用 Swift 声明来表示导入的 C++ 类型和函数。这允许 Swift 代码像使用 Swift 类型和函数一样使用 C++ 类型和函数。

例如,Swift 可以表示来自 forestLib 库的以下 C++ 枚举和以下 C++ 类

enum class TreeKind {
  Oak,
  Redwood,
  Willow
};

class Tree {
public:
  Tree(TreeKind kind);
private:
  TreeKind kind;
};

Swift 编译器在内部使用 Swift 枚举来表示 TreeKind

enum TreeKind : Int32 {
  case Oak = 0
  case Redwood = 1
  case Willow = 2
}

Swift 编译器在内部使用 Swift 结构体来表示 Tree

struct Tree {
  init(_ kind: TreeKind)
}

这种结构体可以直接在 Swift 中使用,就像任何其他 Swift 结构体一样

import forestLib

let tree = Tree(.Oak)

Swift 直接使用 C++ 类型并调用 C++ 函数,没有任何间接或包装。在上面显示的示例中,Swift 直接调用类 Tree 的 C++ 构造函数,并将结果对象直接存储到 tree 变量中。

本指南的后续章节提供了有关 如何在 Swift 中使用 导入的 C++ API 的更多详细信息。

向 C++ 公开 Swift API

除了导入和使用 C++ API 之外,Swift 编译器还能够将 Swift 模块中的 Swift API 公开给 C++。这使得将 Swift 逐步集成到现有的 C++ 代码库中成为可能。

可以通过包含构建系统在构建 Swift 模块时生成的头文件来访问 Swift API。一些构建系统会自动生成头文件。Xcode 可以为框架或 App 目标自动生成头文件。其他构建配置可以通过遵循 项目和构建设置页面 上概述的步骤手动生成头文件。 Swift 函数可以将 C++ 类型作为参数。当从 C++ 中使用这些 Swift API 时,需要在包含生成的互操作头文件之前包含 Swift 函数签名中 C++ 类型的头文件。

生成的头文件使用 C++ 类型和函数来表示 Swift 类型和函数。当启用 C++ 互操作性时,Swift 会为 Swift 模块中所有受支持的公共类型和函数生成 C++ 绑定。例如,可以从 C++ 调用以下 Swift 函数

// Swift module 'forestRenderer'
import forestLib

public func renderTreeToAscii(_ tree: Tree) -> String {
  ...
}

调用 renderTreeToAscii 实现的内联 C++ 函数将出现在 Swift 编译器为 forestRenderer 模块生成的头文件中。一旦 C++ 文件包含生成的头文件,就可以从 C++ 代码中调用它。由于 Swift API 引用了 Tree,这是一种在 C++ 中定义的类型,因此我们需要在生成的头文件之前包含 Tree.hpp

#include "Tree.hpp"
#include "forestRenderer-Swift.h"
#include <string>
#include <iostream>

void printTreeArt(const Tree &tree) {
  std::cout << (std::string)forestRenderer::renderTreeToAscii(tree);
}

C++ 互操作性状态页面 描述了哪些 Swift 语言构造和标准库类型可以公开给 C++。一些不受支持的 Swift API 在生成的头文件中映射到空的、不可用的 C++ 声明,因此当您尝试使用未公开给 C++ 的内容时,您将在 C++ 代码中收到错误。

混合语言代码库的源稳定性保证

Swift 与 C++ 互操作的方式仍在不断发展。未来 Swift 版本的某些更改将需要在已采用 C++ 互操作性的混合 Swift 和 C++ 代码库中进行源代码更改。但是,在采用新版本的 Swift 工具链时,Swift 不会强制您采用新的或已发展的 C++ 互操作性功能。为了实现这一点,未来的 Swift 版本将提供多个 C++ 互操作性的兼容性版本,就像 Swift 为基本 Swift 语言的多个兼容性版本提供支持一样。这意味着使用当前 C++ 互操作性兼容性版本的项目将免受后续版本中所做任何更改的影响,并且可以按照自己的节奏迁移到较新的兼容性版本。

在 Swift 中使用 C++ 类型和函数

Swift 可以直接使用各种 C++ 类型和函数。本节介绍如何在 Swift 中使用受支持的类型和函数的基本原理。

调用 C++ 函数

可以从 Swift 中使用熟悉的函数调用语法来调用从导入模块中的 C++ 函数。例如,这个 C++ 函数在 Swift 中可用

void printWelcomeMessage(const std::string &name);

Swift 代码可以像调用常规 Swift 函数一样调用此类函数

printWelcomeMessage("Thomas")

C++ 结构体和类默认是值类型

默认情况下,Swift 将 C++ 结构体和类映射到 Swift struct 类型。Swift 认为它们是值类型。这意味着当它们在您的 Swift 代码中传递时,总是会被复制。

当 Swift 需要执行值的复制或在值超出作用域时处置值时,Swift 会使用 C++ 结构体或类类型的特殊成员。如果 C++ 类型具有复制构造函数,则当在 Swift 中复制此类类型的值时,Swift 将使用它。如果 C++ 类型具有析构函数,则当销毁此类类型的 Swift 值时,Swift 将调用析构函数。

具有已删除复制构造函数的 C++ 结构体和类表示为不可复制的 Swift 类型 (~Copyable)。如果 C++ 类型具有有效的复制构造函数,仍然可以通过使用 SWIFT_NONCOPYABLE 宏对其进行注释,使其在 Swift 中不可复制。

某些 C++ 类型始终使用指针或引用在 C++ 中传递。因此,将它们映射到 Swift 中的值类型可能没有意义。可以在 C++ 中注释这些类型,以指示 Swift 编译器将它们映射到 Swift 中的引用类型

从 Swift 构造 C++ 类型

C++ 结构体和类中不是复制或移动构造函数的公共构造函数会变成 Swift 中的初始化器。

例如,C++ Color 类的所有三个构造函数在 Swift 中都可用

class Color {
public:
  Color();
  Color(float red, float blue, float green);
  Color(float value);

  ...
  float red, blue, green;
};

上面显示的 Color 构造函数在 Swift 中变成了初始化器。Swift 代码可以调用它们来创建 Color 类型的值

let theEmptiness = Color()
let oceanBlue = Color(0.0, 0.0, 1.0)
let seattleGray = Color(0.7)

访问 C++ 类型的数据成员

C++ 结构体和类的公共数据成员在 Swift 中变成属性。例如,上面显示的 Color 类的数据成员可以像任何其他 Swift 属性一样访问

let color: Color = getRandomColor()
print("Today I'm feeling \(color.red) red but also \(color.blue) blue")

调用 C++ 成员函数

C++ 结构体和类中的成员函数在 Swift 中变成方法。

常量成员函数是 nonmutating nonmutating 章节的永久链接" href="#constant-member-functions-are-nonmutating">

常量成员函数变成 nonmutating Swift 方法,而没有 const 限定符的成员函数变成 mutating Swift 方法。例如,C++ Color 类中的这个成员函数在 Swift 中被认为是 mutating 方法

void Color::invert() { ... }

可变的 Color 值可以调用 invert

var red = Color(1.0, 0.0, 0.0)
red.invert() // red becomes yellow.

常量 Color 值不能调用 invert

另一方面,这个常量成员函数在 Swift 中不是 mutating 方法

Color Color::inverted() const { ... }

因此,常量 Color 值可以调用 inverted

let darkGray = Color(0.2, 0.2, 0.2)
let veryLightGray = darkGray.inverted()

常量成员函数不得修改对象

Swift 编译器假定常量成员函数不会修改 this 指向的实例。如果 C++ 成员函数违反此假设,可能会导致 Swift 代码无法观察到 this 指向的实例的修改,并在 Swift 代码执行的其余部分中使用此类实例的原始值。

C++ 允许在常量成员函数中修改 mutable 字段。具有此类字段的结构体或类中的常量成员函数仍然会变成 Swift 中的 nonmutating 方法。Swift 不知道哪些常量函数修改对象,哪些不修改,因此为了更好的 API 可用性,Swift 仍然假定此类函数不修改对象。您应该避免从 Swift 调用修改 mutable 字段的常量成员函数,除非它们显式使用 SWIFT_MUTATING 宏进行注释。

SWIFT_MUTATING 宏允许您显式注释确实会修改对象的常量成员函数。然后,此类函数在 Swift 中变成 mutating 方法。

默认情况下,返回引用的成员函数是不安全的

返回引用、指针或某些包含引用或指针的结构体/类的成员函数通常返回指向 this 内部的引用,即用于调用函数的对象。此类成员函数在 Swift 中被认为是不安全的,因为返回的引用未与拥有对象关联,而拥有对象可能会在引用仍在使用时被销毁。Swift 会自动重命名此类成员函数,以强调其不安全性。它们的 Swift 名称以两个下划线为前缀,并以 Unsafe 为后缀。例如,以下成员函数在 Swift 中变为 __getRootTreeUnsafe 方法

class Forest {
public:
  const Tree &getRootTree() const { return rootTree; }

  ...
private:
  Tree rootTree;
};

确定哪些函数是不安全的规则集,以及从 Swift 安全调用此类方法的建议指南,将在即将到来的章节中记录,该章节描述了如何在 Swift 中安全地使用 C++ 引用和视图类型

重载成员函数

C++ 允许根据成员函数的 const 限定符进行重载。例如,Forest 类可以有两个 getRootTree 成员,它们仅在常量性和返回类型上有所不同

class Forest {
public:
  const Tree &getRootTree() const { return rootTree; }
  Tree &getRootTree() { return rootTree; }

  ...
private:
  Tree rootTree;
};

两个 getRootTree 成员函数在 Swift 中变成方法。当 Swift 发现该类型已经有一个具有相同 Swift 名称的 nonmutating 方法时,Swift 会重命名 mutating 方法,以避免出现两个具有相同名称和参数的歧义方法。重命名会将 Mutating 后缀附加到 mutating 方法的名称。此重命名在考虑方法的安全性之前完成。在上面显示的示例中,两个 getRootTree 成员函数在 Swift 中变成 __getRootTreeUnsafe__getRootTreeMutatingUnsafe 方法,因为它们返回指向 Forest 对象的引用。

虚成员函数

目前,虚成员函数在 Swift 中不可用。

静态成员函数

静态 C++ 成员函数变成 static Swift 方法。

从 Swift 访问继承的成员

C++ 类或结构体在 Swift 中会变成独立的类型。它与 C++ 基类的关系在 Swift 中不会被保留。Swift 会尽力提供对从 C++ 类型的基类继承的成员的访问。来自 C++ 基类的公共成员函数和数据成员会变成 Swift 中的方法和属性,就好像它们是在该特定类本身中定义的一样。

例如,以下两个 C++ 类会变成两个不同的 Swift 结构体

class Plant {
public:
  void water(float amount) { moisture += amount; }
private:
  float moisture = 0.0;
};

class Fern: public Plant {
public:
  void trim();
};

Fern Swift 结构体获得了一个名为 water 的额外方法,该方法调用 C++ Plant 类中的成员函数 water

struct Plant {
  mutating func water(_ amount: Float)
}

struct Fern {
  init()
  mutating func water(_ amount: Float) // Calls `Plant::water`
  mutating func trim()
}

在 Swift 5.9 中,确定何时将继承自基类型的成员引入到表示 C++ 结构体或类的 Swift 类型的确切规则尚未最终确定。以下 GitHub issue 跟踪了它们在 Swift 5.9 中的最终确定。

使用 C++ 枚举

作用域 C++ 枚举会变成带有原始值的 Swift 枚举。它们的所有 case 也会映射到 Swift 的 case。例如,以下 C++ 枚举在 Swift 中可用

enum class TreeKind {
  Oak,
  Redwood,
  Willow
};

它在 Swift 中表示为以下枚举

enum TreeKind : Int32 {
  case Oak = 0
  case Redwood = 1
  case Willow = 2
}

它可以像 Swift 中的任何其他 enum 一样使用

func isConiferous(treeKind: TreeKind) -> Bool {
  switch treeKind {
    case .Redwood: return true
    default: return false
  }
}

无作用域 C++ 枚举会变成 Swift 结构体。例如,以下无作用域 enum 会变成一个 Swift 结构体

enum MushroomKind {
  Oyster,
  Portobello,
  Button
}

无作用域 C++ 枚举的 case 会变成 Swift 结构体外部的变量

struct MushroomKind : Equatable, RawRepresentable {
    public init(_ rawValue: UInt32)
    public init(rawValue: UInt32)
    public var rawValue: UInt32
}
var Oyster: MushroomKind { get }
var Portobello: MushroomKind { get }
var Button: MushroomKind { get }

使用 C++ 类型别名

C++ 的 usingtypedef 声明会变成 Swift 中的 typealias。例如,以下 using 声明会变成 Swift 中的 CustomString 类型

using CustomString = std::string;

使用类模板

类或结构体模板的实例化特化会映射到 Swift 中的不同类型。例如,以下未实例化的 C++ 类模板在 Swift 中本身不可用

template<class T, class U>
class Fraction {
public:
  T numerator;
  U denominator;

  Fraction(const T &, const U &);
};

但是,类模板的实例化特化在 Swift 中可用。当它映射到 Swift 中时,它被视为常规 C++ 结构体或类。例如,Fraction<int, float> 模板特化会变成一个 Swift 结构体

struct Fraction<CInt, Float> {
  var numerator: CInt
  var denominator: Float

  init(_: CInt, _: Float)
}

返回特化(如 Fraction<int, float>)的函数在 Swift 中可用

Fraction<int, float> getMagicNumber();

这样的函数可以像任何其他 Swift 函数一样从 Swift 中调用

let magicNum = getMagicNumber()
print(magicNum.numerator, magicNum.denominator)

C++ 类型别名可以引用类模板的特定特化。例如,为了从 Swift 构造 Fraction<int, float>,您首先需要创建一个 C++ 类型别名来引用这样的模板特化

// Bring `Fraction<int, float>` type to Swift with a C++ `using` declaration.
using MagicFraction = Fraction<int, float>;

然后您可以直接从 Swift 中使用此类型别名

let oneEights = MagicFraction(1, 8.0)
print(oneEights.numerator)

本文档的 后续章节 描述了如何使用 Swift 泛型和协议扩展来编写可用于任何类模板特化的通用 Swift 代码。

自定义 C++ 到 Swift 的映射方式

可以通过使用提供的自定义宏之一来注释特定的 C++ 函数或类型,从而更改确定 C++ 类型和函数如何映射到 Swift 的默认行为。例如,您可以使用 SWIFT_NAME 宏为特定的 C++ 函数选择提供不同的 Swift 名称。

<swift/bridging> 头文件定义了可用于注释 C++ 函数和类型的自定义宏。此头文件随 Swift 工具链一起提供。

在 Apple 和 Linux 平台上,系统 C++ 编译器和 Swift 编译器都应自动找到此头文件。在其他平台(如 Windows)上,您可能需要向 C++ 和 Swift 编译器调用添加额外的头文件搜索路径标志 (-I),以确保找到此头文件。

本节仅介绍 <swift/bridging> 头文件中的两个自定义宏。其他自定义宏及其行为将在本文档的后续章节中介绍。完整列表 的所有自定义宏都在附录中提供。

在 Swift 中重命名 C++ API

SWIFT_NAME 宏为 Swift 中的 C++ 类型和函数提供了不同的名称。可以通过在 SWIFT_NAME 宏中指定 Swift 类型名称来重命名 C++ 类型。例如,以下 C++ 类在 Swift 中被重命名为 CxxLibraryError 结构体

class Error {
  ...
} SWIFT_NAME("CxxLibraryError");

重命名函数时,需要在 SWIFT_NAME 宏中指定 Swift 函数名称(包括参数标签)。例如,以下 C++ 函数在 Swift 中被重命名为 send

#include <swift/bridging>

void sendCopy(const std::string &) SWIFT_NAME(send(_:));

从 Swift 调用此类函数时,必须使用新名称

send("Hello, this is Swift!")

将 Getter 和 Setter 映射到计算属性

SWIFT_COMPUTED_PROPERTY 宏将 C++ getter 和 setter 成员函数映射到 Swift 中的计算属性。例如,以下 getter 和 setter 对被映射到 Swift 中的单个 treeKind 计算属性

#include <swift/bridging>

class Tree {
public:
  TreeKind getKind() const SWIFT_COMPUTED_PROPERTY;
  void setKind(TreeKind kind) SWIFT_COMPUTED_PROPERTY;

  ...
};

由于此属性具有 setter,因此可以在 Swift 中对其进行修改

func makeNotAConiferousTree(tree: inout Tree) {
  tree.kind = tree.kind == .Redwood ? .Oak : tree.kind
}

对于此转换在 Swift 中成功,getter 和 setter 都需要对相同的底层 C++ 类型进行操作。

可以将仅 getter 映射到计算属性,此转换不需要 setter 即可工作。

在 Swift 中扩展 C++ 类型

Swift 扩展 可以为 Swift 中的 C++ 类型添加新功能。它们还可以使现有的 C++ 类型符合 Swift 协议。

扩展可以为 C++ 类型添加新功能,但它们无法覆盖 C++ 类型的现有功能。

使 C++ 类型符合 Swift 协议

Swift 协议一致性可以追溯地添加到 C++ 类型(在类型定义之后)。这种一致性使 Swift 中能够实现以下用例

例如,Swift 扩展可以为 C++ 类 Tree 添加 Hashable 一致性

extension Tree: Hashable {
  static func == (lhs: Tree, rhs: Tree) -> Bool {
    return lhs.kind == rhs.kind
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(self.kind.rawValue)
  }
}

这种一致性使您可以在 Swift 字典中使用 Tree 作为键

let treeEmoji: Dictionary<Tree, String> = [
  Tree(.Oak): "🌳",
  Tree(.Redwood): "🌲"
]

使类模板符合 Swift 协议

Swift 扩展可以为 Swift 中的特定类模板特化添加协议一致性。例如,以下类模板的实例化特化在 Swift 中可用

template<class T>
class SerializedValue {
public:
  T deserialize() const;

  ...
};

using SerializedInt = SerializedValue<int>;
using SerializedFloat = SerializedValue<float>;

SerializedInt getSerializedInt();
SerializedFloat getSerializedFloat();

此类模板特化可以使用 Swift extension 符合协议

// Swift module 'Serialization'
protocol Deserializable {
  associatedtype ValueType

  func deserialize() -> ValueType
}

// `SerializedInt` specialization now conforms to `Deserializable`
extension SerializedInt: Deserializable {}

在上面的示例中,SerializedInt 符合 Deserializable 协议。但是,类模板的其他特化(如 SerializedFloat)不符合 Deserializable

来自 <swift/bridging> 头文件的 SWIFT_CONFORMS_TO_PROTOCOL 自定义宏可用于使类模板的所有特化自动符合 Swift 协议。例如,SerializedValue 类模板的定义可以使用 SWIFT_CONFORMS_TO_PROTOCOL 进行注释

template<class T>
class SerializedValue {
public:
  using ValueType = T;
  T deserialize() const;

  ...
} SWIFT_CONFORMS_TO_PROTOCOL(Serialization.Deserializable);

SWIFT_CONFORMS_TO_PROTOCOL 注释使所有特化(如 SerializedIntSerializedFloat)在 Swift 中自动符合 Deserializable。这使得可以通过使用协议扩展向类模板的所有特化添加功能

extension Deserializable {
  // All specializations of the `SerializedValue` template now have
  // `deserializedDescription` property in Swift.
  var deserializedDescription: String {
    "serialized value \(deserialize().description)"
  }
}

这也使您可以在受约束的泛型代码中使用任何特化,而无需任何额外的显式一致性

func printDeserialized<T: Deserializable>(_ item: T) {
  print("obtained: \(item.deserializedDescription)")
}

// Both `SerializedInt` and `SerializedFloat` specializations automatically
// conform to `Deserializable`
printDeserialized(getSerializedInt())
printDeserialized(getSerializedFloat())

使用 C++ 容器

C++ 容器类型(如 std::vector 类模板)通常为 C++ 中的用户提供基于迭代器的 API。在 Swift 中使用 C++ 迭代器是 不安全的,因为这种使用与它的拥有容器无关,而容器可能会在迭代器仍在使用时被销毁。Swift 没有依赖 C++ 迭代器,而是自动使某些 C++ 容器类型符合以下协议

这些协议及其一致性规则在下面描述。使用符合这些协议的 C++ 容器的推荐方法在 后续章节 中进行了总结。

一些 C++ 容器是 Swift 集合

Swift 使提供对其元素随机访问的 C++ 容器(如 std::vector)自动遵循 Swift 的 RandomAccessCollection 协议。例如,此函数返回的 std::vector 容器由 Swift 自动遵循 RandomAccessCollection 协议。

std::vector<Tree> getEnchantedTrees();

遵循 RandomAccessCollection 协议使得在 Swift 中安全地遍历容器的元素成为可能,可以使用熟悉的控制流语句(如 for-in 循环)。集合方法(如 mapfilter)也可用。

let trees = getEnchantedTrees()

// Traverse through the elements of a C++ vector.
for tree in trees {
  print(tree.kind)
}

// Filter the C++ vector and make a Swift Array that contains only
// the oak trees.
let oakTrees = getEnchantedTrees().filter { $0.kind == .Oak }

Swift 的 count 属性返回此类集合中元素的数量。Swift 的下标运算符也可用于访问集合中的特定元素。这使得可以修改 C++ 容器中的单个元素。

var trees = getEnchantedTrees()
for i in 0..<trees.count {
  trees[i].kind = .Oak
}

符合 RandomAccessCollection 协议的 C++ 容器可以轻松转换为 Swift 集合类型,例如 Array

let treesArray = Array<Tree>(getEnchantedTrees())

Swift **不会**自动将 C++ 容器类型转换为 Swift 集合类型。从 C++ 容器类型(如 std::vector)到 Swift 集合类型(如 Array)的任何转换在 Swift 中都是显式的。

自动集合一致性的性能约束

Swift 目前在使用符合 RandomAccessCollection 协议的 C++ 容器时,不提供明确的性能保证。在以下情况下,Swift 最有可能对容器进行深拷贝:

此约束在状态页上跟踪。下面介绍了几种解决此约束的策略

随机访问 C++ 集合的一致性规则

为了使 C++ 容器类型在 Swift 中自动遵循 RandomAccessCollection 协议,必须满足以下两个条件:

当满足这些条件时,Swift 会使表示底层 C++ 容器类型的 Swift 结构遵循 CxxRandomAccessCollection 协议,从而添加 RandomAccessCollection 一致性。

C++ 容器可以转换为 Swift 集合

不提供对其元素随机访问的顺序 C++ 容器类型在 Swift 中自动遵循 CxxConvertibleToCollection 协议。例如,此函数返回的 std::set 容器由 Swift 自动遵循 CxxConvertibleToCollection 协议。

std::set<int> getWinningNumers();

遵循 CxxConvertibleToCollection 协议使得轻松将 C++ 容器转换为 Swift 集合类型(如 ArraySet)成为可能。例如,getWinningNumers 返回的 std::set 可以转换为 Swift Array 和 Swift Set

let winners = getWinningNumers()
for number in Array(winners) {
  print(number)
}
let setOfWinners = Set(winners)

自动遵循 CxxRandomAccessCollection 协议的 C++ 容器也自动遵循 CxxConvertibleToCollection 协议。

CxxConvertibleToCollection 协议的一致性规则 CxxConvertibleToCollection Protocol section 的永久链接" href="#conformance-rules-for-cxxconvertibletocollection-protocol">

为了使 C++ 容器类型在 Swift 中自动遵循 CxxConvertibleToCollection 协议,必须满足以下两个条件:

在 Swift 中使用关联容器 C++ 类型

关联 C++ 容器类型(如 std::map)使用查找键提供对存储元素的有效访问。执行此类查找的 find 成员函数在 Swift 中是不安全的。Swift 没有使用 find,而是自动使 C++ 标准库中的关联容器遵循 CxxDictionary 协议。这种一致性使您在 Swift 中使用关联 C++ 容器时可以使用下标运算符。例如,此函数返回的 std::unordered_map 由 Swift 自动遵循 CxxDictionary 协议。

std::unordered_map<std::string, std::string>
getAirportCodeToCityMappings();

返回的 std::unordered_map 值可以在 Swift 中像字典一样使用,下标返回存储在容器中的值,如果该值不存在,则返回 nil

let mapping = getAirportCodeToCityMappings();
if let dubCity = mapping["DUB"] {
   print(dubCity)
}

提供的下标在其实现内部安全地调用容器的 find 方法。

当您需要在 Swift 中手动遍历关联 C++ 容器的元素时,可以将它们转换为 Swift 顺序集合类型(如 Array)。

Swift 不会自动使自定义关联 C++ 容器遵循 CxxDictionary 协议。手动编写的 Swift extension 可用于为自定义关联容器类型追溯添加 CxxDictionary 一致性。

以下摘要概述了当前推荐的在 Swift 中使用 C++ 容器的方法:

在性能敏感的 Swift 代码中使用 C++ 容器

Swift 当前的 for-in 循环在遍历 C++ 容器的元素时会对其进行深拷贝。您可以使用 CxxConvertibleToCollection 协议提供的 forEach 方法来避免此拷贝。例如,可以使用 Swift 中的 forEach 方法遍历 getEnchantedTrees 返回的 std::vector<Tree> 容器。

let trees = getEnchantedTrees()
// Swift should not copy the `trees` std::vector here.
trees.forEach { tree in
  print(tree.kind)
}

在 Swift 中使用 C++ 容器的最佳实践

不要在 Swift 中使用 C++ 迭代器

正如本节开头所述,在 Swift 中使用 C++ 迭代器是不安全的。很容易误用 C++ 迭代器,例如:

在使用 C++ 容器时,您应该使用诸如 CxxRandomAccessCollectionCxxConvertibleToCollectionCxxDictionary 等协议,而不是依赖 C++ 迭代器 API。

C++ 容器类型内部返回 C++ 迭代器的成员函数在 Swift 中被标记为不安全,就像返回引用的成员函数一样。其他 C++ API,例如接受或返回迭代器的顶级函数,仍然可以在 Swift 中直接使用。您应该避免在 Swift 中使用此类函数。

在调用 Swift 函数时借用 C++ 容器

C++ 容器类型在 Swift 中变为值类型。这意味着每次在 Swift 中进行复制时,Swift 都会调用容器的复制构造函数,而复制构造函数又会复制所有元素。例如,每当将由 CxxVectorOfInt 类型表示的 std::vector<int> 传递到此 Swift 函数中时,Swift 都会将所有元素复制到新的向量中。

func takesVectorType(_ : CxxVectorOfInt) {
  ...
}

let vector = createCxxVectorOfInt()
takesVectorType(vector) // 'vector' is copied here.

Swift 即将推出的参数所有权修饰符将在即将到来的 Swift 版本中提供,它将使您在将不可变值传递给函数时避免复制。可变值可以通过 inout 传递给 Swift 函数,这使您可以避免 C++ 容器的深拷贝。

func mutatesVectorType(_ : inout CxxVectorOfInt) {
  ...
}

var vector = createCxxVectorOfInt()
takesVectorType(&vector) // 'vector' is not copied!

将 C++ 类型映射到 Swift 引用类型

Swift 编译器允许您注释某些 C++ 类型,并将它们作为引用类型(或类类型)导入到 Swift 中。C++ 类型是否应作为引用类型导入是一个复杂的问题,回答这个问题有两个主要标准。

第一个标准是对象标识是否是类型“值”的一部分。比较两个对象的地址只是询问它们是否存储在同一位置,还是在更重要的意义上决定它们是否代表“同一对象”?

第二个标准是 C++ 类的对象是否总是通过引用传递。对象是否主要通过指针或引用类型传递,例如原始指针 (*)、C++ 引用 (&&&) 或智能指针(如 std::unique_ptrstd::shared_ptr)?当通过原始指针或引用传递时,是否期望内存是稳定的并且将继续保持有效,还是接收者如果需要独立地保持值的生命周期,则需要复制对象?如果对象通常被分配并且保持在稳定的地址,即使该地址在语义上不是对象“值”的一部分,则该类在惯用上可能是一种引用类型。对于程序员来说,这有时需要判断。

对于编译器来说,第一个也是最重要的标准通常无法仅通过查看代码自动回答。如果您希望 Swift 编译器将 C++ 类型映射到 Swift 引用类型,则必须使用来自 <swift/bridging> 标头的以下自定义宏之一来注释 C++ 类型

不朽引用类型

不朽引用类型并非设计为由程序单独管理。这些类型的对象被分配,然后故意“泄漏”,而不跟踪其使用情况。有时这些对象并非真正不朽:例如,它们可能是竞技场分配的,期望它们仅从竞技场内的其他对象引用。尽管如此,它们不应被单独管理。

Swift 对不朽引用类型唯一合理的处理方式是将它们作为非托管类导入。当对象真正不朽时,这完全没问题。如果对象是竞技场分配的,则这是不安全的,但这本质上是 C++ API 选择下不可避免的不安全级别。

要指定 C++ 类型是不朽引用类型,请应用 SWIFT_IMMORTAL_REFERENCE 属性。以下是将 SWIFT_IMMORTAL_REFERENCE 应用于 C++ 类型 LoggerSingleton 的示例

class LoggerSingleton {
public:
    LoggerSingleton(const LoggerSingleton &) = delete; // non-copyable

    static LoggerSingleton &getInstance();
    void log(int x);
} SWIFT_IMMORTAL_REFERENCE;

现在 LoggerSingleton 在 Swift 中作为引用类型导入,程序员将能够通过以下方式使用它

let logger = LoggerSingleton.getInstance()
logger.log(123)

共享引用类型

共享引用类型是以指针或引用在 C++ 中传递的引用计数类型。它们通常使用以下两种方式之一:

目前,Swift 可以将使用自定义 retain 和 release 操作以及存储在对象内部的引用计数的 C++ 类或结构体映射到 Swift 引用类型(其行为类似于 Swift class)。其他依赖 std::shared_ptr 进行引用计数的类型仍然可以在 Swift 中用作值类型。

要指定 C++ 类型是共享引用类型,请使用 SWIFT_SHARED_REFERENCE 自定义宏。此宏需要两个参数:retain 和 release 函数。这些函数必须是全局函数,它们接受正好一个参数并返回 void。参数必须是指向 C++ 类型的指针(而不是基类型)。Swift 将在它通常 retain 和 release Swift 类的地方调用这些自定义 retain 和 release 函数。以下是将 SWIFT_SHARED_REFERENCE 应用于 C++ 类型 SharedObject 的示例

class SharedObject : IntrusiveReferenceCounted<SharedObject> {
public:
    SharedObject(const SharedObject &) = delete; // non-copyable

    static SharedObject* create();
    void doSomething();
} SWIFT_SHARED_REFERENCE(retainSharedObject, releaseSharedObject);

void retainSharedObject(SharedObject *);
void releaseSharedObject(SharedObject *);

现在 SharedObject 在 Swift 中作为引用类型导入,程序员将能够通过以下方式使用它

let object = SharedObject.create()
object.doSomething()
// `object` will be released here.

从 Swift 中暴露 C++ 共享引用类型

C++ 可以调用接受或返回 C++ 共享引用类型的 Swift API。这些类型的对象始终在 C++ 端创建,但它们的引用可以在 Swift 和 C++ 之间来回传递。本节解释了在跨语言边界传递此类引用时,递增和递减引用计数的约定。考虑以下 Swift API

public func takeSharedObject(_ x : SharedObject) { ... }

public func returnSharedObject() -> SharedObject { ... }

对于 takeSharedObject 函数,编译器将自动插入对 x 的 retain 和 release 调用,以满足 owned/guaranteed 调用约定的语义。C++ 调用者必须保证 x 在调用期间是存活的。请注意,返回共享引用类型的函数(如 returnSharedObject)将所有权转移给调用者。此函数的 C++ 调用者负责 release 对象。

不安全引用类型

SWIFT_UNSAFE_REFERENCE 注解宏与 SWIFT_IMMORTAL_REFERENCE 注解宏具有相同的效果。但是,它传达了不同的语义:该类型旨在不安全地使用,而不是在程序运行期间存活。

唯一引用类型

Swift 尚不支持唯一引用类型,例如通过 std::unique_ptr 传递的类型。

从 Swift 中使用 C++ 标准库

本节描述如何导入 C++ 标准库,以及如何在 Swift 中使用它提供的类型。

导入 C++ 标准库

Swift 可以通过导入 CxxStdlib 模块来导入平台的 C++ 标准库。std 命名空间在 Swift 中变为 std 枚举。std 命名空间内的函数和类型在 std Swift 枚举中变为嵌套类型和静态函数。

状态页面包含一个 受支持的 C++ 标准库列表,其中描述了 Swift 支持的平台上支持哪些 C++ 标准库。

使用 std::string

std::string C++ 类型在 Swift 中变为结构体。它符合 ExpressibleByStringLiteral 协议,因此可以直接在 Swift 中使用字符串字面量进行初始化

import CxxStdlib

let s: std.string = "Hello C++ world!"

Swift String 可以轻松转换为 C++ std::string

let swiftString = "This is " + "a Swift string"
let cxxString = std.string(swiftString)

相同的转换可以在相反的方向进行,从 C++ std::string 转换为 Swift String

let cxxString = std.string("This is a C++ string")
let swiftString = String(cxxString)

Swift 不会自动将 C++ std::string 类型转换为 Swift 的 String 类型。

在 Swift 中使用 C++ 引用和视图类型

前面 所述,返回引用、指针或某些包含引用或指针的结构体/类的成员函数在 Swift 中被认为是不安全的。此类成员函数通常返回指向 this 对象内部或 this 对象拥有的内存的引用或视图类型。在这种情况下,返回的引用或视图指向的对象的生命周期被认为是依赖于其所有者对象(传递给成员函数的 this 对象)的生命周期。C++ 目前未指定哪些成员函数返回依赖引用或视图,哪些成员函数返回完全独立的引用或视图。因此,Swift 假定成员函数返回的任何引用或任何视图类型都依赖于 this 对象。

依赖引用和视图类型在 Swift 中是不安全的,因为引用或视图未与其所有者对象关联。因此,所有者对象可能会在引用仍在使用时被销毁。由于这种不安全性,以及所有此类引用和视图都是依赖的假设,Swift 会重命名此类成员函数以强调其不安全性,并劝阻在 Swift 中使用它们。

本节描述了 Swift 用于确定哪些成员函数返回依赖引用或视图类型的确切规则,并提出了关于如何编写 Swift 包装器以从 Swift 安全地调用此类成员函数的方法。它还介绍了两个新的自定义宏,可以应用于 C++ 代码,以指示 Swift 将其认为不安全的一些成员函数视为安全。

Swift 认为属于引用或视图类型的 C++ 类型

Swift 假定返回以下类型之一的 C++ 成员函数在 Swift 中是不安全的

例如,以下两个 C++ 结构体在 Swift 看来是视图类型

struct PairIntRefs {
  int &firstValue;
  const int &secondImmutableValue;

  PairIntRefs(int &, const int &);
};

// Also a view type, since its `refs` field is a view type.
struct BagOfValues {
  PairIntRefs refs;
  int x;

  BagOfValues(PairIntRefs, int);
};

上面概述的规则定义了 Swift 用于检测可能返回依赖引用或视图类型的成员函数的启发式方法。此启发式方法不能保证 Swift 会检测到所有返回依赖引用或视图类型的 C++ 成员函数,因此某些返回依赖引用或视图类型的成员函数可能在 Swift 中看起来是安全的。

安全地访问具有依赖生命周期的引用

调用返回具有依赖生命周期的引用或视图类型的成员函数的推荐方法是将它们包装在返回引用对象副本的 Swift API 中。

例如,考虑 Forest 类,其 getRootTree 成员返回引用

class Forest {
public:
  const Tree &getRootTree() const { return rootTree; }
  Tree &getRootTree() { return rootTree; }

  ...
private:
  Tree rootTree;
};

前面 所述,这两个成员函数在 Swift 中都变为 __getRootTreeUnsafe__getRootTreeMutatingUnsafe 方法。这些方法不应直接在您的代码中调用。相反,您应该编写一个包装器,在不暴露依赖引用的情况下实现所需的目标,然后可以在整个 Swift 代码库中使用。例如,假设您想在 Swift 中检查底层的 rootTree 值。您可以添加一个包装器,通过在 Swift 中扩展 Forest 类并添加返回 Tree 值的 rootTree 计算属性,使您的 Swift 代码库能够检查 rootTree

import forestLib

extension Forest {
  private borrowing func getRootTreeCopy() -> Tree {
    return __getRootTreeUnsafe().pointee
  }

  var rootTree: Tree {
    getRootTreeCopy()
  }
}

上述 borrowing 所有权修饰符是 Swift 5.9 中的新特性。 Swift 5.9 的某些开发版本可能不允许您对可复制的 C++ 类型(如 Forest)使用 borrowing。 在这种情况下,在 Swift 5.9 发布之前,您可以改用 mutating 方法调用链来安全地复制由 getRootTree 返回的 Tree

import forestLib

extension Forest {
  private mutating func getRootTreeCopy() -> Tree {
    return __getRootTreeUnsafeMutating().pointee
  }

  var rootTree: Tree {
    var mutCopy = self
    return mutCopy.getRootTreeCopy()
  }
}

使用返回具有独立生命周期的引用和视图的方法

一些返回引用或视图类型的 C++ 成员函数可能会返回一个引用,该引用的生命周期独立于 this 对象。 Swift 仍然会假定此类成员函数是不安全的。 要更改此行为,您可以注释您的 C++ 代码,以指示 Swift 执行以下操作之一:

注释返回独立引用或视图的方法

可以向 C++ 成员函数添加来自 <swift/bridging> 标头文件的 SWIFT_RETURNS_INDEPENDENT_VALUE 自定义宏,以告知 Swift 它不返回依赖引用或依赖视图。 那么此类成员函数在 Swift 中将被假定为安全的。

例如,NatureLibrary C++ 类中的 getName 成员函数是 SWIFT_RETURNS_INDEPENDENT_VALUE 的绝佳候选者,因为它的定义清楚地表明它返回一个指向常量静态字符串文字的指针,该指针未存储在 NatureLibrary 对象本身中。

class NatureLibrary {
public:
  const char *getName() const SWIFT_RETURNS_INDEPENDENT_VALUE {
    return "NatureLibrary";
  }
};

将 C++ 结构体或类注释为自包含

可以向 C++ 结构体或类添加来自 <swift/bridging> 标头文件的 SWIFT_SELF_CONTAINED 自定义宏,以告知 Swift 它不是视图类型。 那么所有返回此类自包含类型的成员函数在 Swift 中将被假定为安全的。

从 C++ 访问 Swift API

Swift 编译器可以生成一个标头文件,其中包含表示 Swift 模块中定义的 Swift 类型和函数的 C++ 类型和函数。 然后可以将此标头文件从 C++ 代码中包含进来,从而允许您从 C++ 中使用 Swift 类型并调用 Swift 函数。

在生成生成的标头文件时,Swift 会将 Swift 模块中定义的所有公共类型和函数视为符合暴露给 C++ 的条件。 但是,并非所有公共类型和函数都可以在 C++ 中表示。 确定哪些 Swift 类型和函数当前在生成的标头中暴露给 C++ 的确切规则在以下状态页面部分中进行了描述。

从 C++ 中使用 Swift 类型和函数

各种各样的 Swift 类型和函数被暴露给 C++。 本节将介绍如何从 C++ 中使用暴露的 Swift 类型和函数的基础知识。

调用 Swift 函数

暴露给 C++ 的顶层 Swift 函数在生成的标头中变为 inline C++ 函数。 C++ 函数放置在表示 Swift 模块的 C++ namespace 中。 此类 C++ 函数的主体直接从 C++ 调用原生 Swift 函数,而无需使用任何形式的间接调用。

例如,以下 Swift 函数在生成的标头中暴露给 C++

// Swift module 'Greeter'

public func printWelcomeMessage(_ name: String) {
  print("Welcome \(name)")
}

C++ 代码可以在包含生成的标头后调用 printWelcomeMessage

#include <Greeter-Swift.h>

void cPlusPlusCallsSwift() {
  Greeter::printWelcomeMessage("Theo");
}

在 C++ 中使用 Swift 结构体

暴露给 C++ 的 Swift 结构体在生成的标头中变为 final C++ 类。 顶层结构体放置在表示 Swift 模块的 C++ namespace 中。 在 Swift 结构体内部定义的暴露的初始化器、方法和属性成为 C++ 类的成员。

表示 Swift 结构体的 C++ 类是可复制的。 它的复制构造函数将底层 Swift 值复制到新值中。 C++ 类的析构函数销毁底层 Swift 值。

目前,表示 Swift 结构体的 C++ 类无法在 C++ 中使用 std::move 移动。

在 C++ 中创建 Swift 结构体

Swift 结构体的暴露的初始化器在 C++ 类中变为静态 init 成员函数。 然后,C++ 代码可以调用此类函数之一以在 C++ 中创建结构体的实例。

例如,Swift 在生成的标头中暴露了如下所示的 MountainPeak 结构体

// Swift module 'Landscape'

public struct MountainPeak {
  let name: String
  let height: Float

  public init(name: String, height: Float) {
    self.name = name
    self.height = height
  }
}

来自 MountainPeak C++ 类的 init 静态成员函数可用于在 C++ 中创建 MountainPeak 实例

#include <Landscape-Swift.h>
using namespace Landscape;

void createMountainRange() {
  auto tallestMountain = MountainPeak::init("Everest", 8848.9);
}

在 C++ 中使用 Swift 类

暴露给 C++ 的 Swift 类在生成的标头中变为 C++ 类。 顶层类放置在表示 Swift 模块的 C++ namespace 中。 在 Swift 类内部定义的暴露的初始化器、方法和属性成为 C++ 类的成员。

表示 Swift 类的 C++ 类是可复制和可移动的。 它的复制和移动构造函数及其析构函数遵循 Swift 的自动引用计数 (ARC) 模型的规则,该模型允许程序在不再引用 Swift 类实例时释放它。

例如,Swift 在生成的标头中暴露了如下所示的 MountainRange

// Swift module 'Landscape'

public class MountainRange {
  let peaks: [MountainPeak]

  public init(peaks: [MountainPeak]) {
    self.peaks = peaks
  }
}

public func createSierras() -> MountainRange {
  ...
}

public func render(mountainRange: MountainRange) {
  ...
}

然后可以在 C++ 中安全地传递 MountainRange 实例。 一旦不再使用,ARC 将释放它。

#include <Landscape-Swift.h>
using namespace Landscape;

void renderSierras() {
  MountainRange range = createSierras();
  render(range);
  // The `MountainRange` instance that `range` points to is freed by ARC when
  // this C++ function returns.
}

Swift 类的继承层次结构使用由表示暴露的 Swift 类的 C++ 类形成的 C++ 继承层次结构来表示。

在 C++ 中使用 Swift 枚举

暴露给 C++ 的 Swift 枚举在生成的标头中变为 C++ 类。 顶层枚举放置在表示 Swift 模块的 C++ namespace 中。 在 Swift 枚举内部定义的暴露的初始化器、方法和属性成为 C++ 类的成员。

表示 Swift 枚举的 C++ 类是可复制的。 它的复制构造函数将底层 Swift 值复制到新值中。 C++ 类的析构函数销毁底层 Swift 值。 目前,表示 Swift 枚举的 C++ 类无法在 C++ 中使用 std::move 移动。

枚举 case 在表示枚举的 C++ 类中变为 static inline 常量 C++ 数据成员。 这些成员允许您:

例如,以下 Swift 枚举在生成的标头中暴露

// Swift module 'Landscape'

public enum VolcanoStatus {
  case dormant
  case active
}

可以通过对表示枚举 case 的成员之一使用 operator() 从 C++ 构造 VolcanoStatus 实例。 您还可以在 C++ 中 switch 语句内的 case 条件中引用此类成员。

#include <Landscape-Swift.h>
using namespace Landscape;

VolcanoStatus invertVolcanoStatus(VolcanoStatus status) {
  switch (status) {
  case VolcanoStatus::dormant:
    return VolcanoStatus::active(); // Returns `VolcanoStatus.active` case.
  case VolcanoStatus::active:
    return VolcanoStatus::dormant(); // Returns `VolcanoStatus.dormant` case.
  }
}

unknownDefault C++ 成员允许您为弹性 Swift 枚举编写详尽的 C++ switch,因为此类枚举将来可能会获得 C++ 代码不知道的更多 case。

使用具有关联值的枚举

Swift 允许枚举将一组值与特定的 case 相关联。 其 case 具有一个关联值或没有关联值的枚举会暴露给 C++。 它们在生成的标头中变为 C++ 类。 此类 C++ 类的接口与为没有关联值的 Swift 枚举生成的类的接口非常相似。 此类还包含额外的 getter 成员函数,一旦您确定枚举设置为哪个 case,这些函数允许您提取存储在枚举中的关联值。

例如,以下具有关联值的 Swift 枚举在生成的标头中暴露

// Swift module 'Landscape'

public enum LandmarkIdentifier {
  case name(String)
  case id(Int)
}

可以通过在 C++ 中调用适当的 getter 方法来提取与 LandmarkIdentifier 的 case 之一关联的值

#include <Landscape-Swift.h>
#include <iostream>
using namespace Landscape;

void printLandmarkIdentifier(LandmarkIdentifier identifier) {
  switch (status) {
  case LandmarkIdentifier::name:
    std::cout << (std::string)identifier.getName();
    break;
  case LandmarkIdentifier::id:
    std::cout << "unnamed landmark #" << identifier.getId();
    break;
  }
}

也可以从 C++ 构造一个新的 LandmarkIdentifier 实例

auto newLandmarkId = LandmarkIdentifier::id(1234);

调用 Swift 方法

Swift 方法在 C++ 中变为成员函数。

Swift 结构体和枚举具有 mutatingnonmutating 方法。 Nonmutating 方法在 C++ 中变为常量成员函数。

在 C++ 中访问 Swift 属性

存储属性和计算属性在 C++ 中都变成了 getter 和 setter 成员函数。getter 是一个常量成员函数,返回 Swift 属性的值。可变属性在 C++ 中也有 setter。setter 是一个成员函数,不应在 Swift 值类型的不可变实例上调用。

例如,以下 Swift 结构在生成的头文件中暴露给 C++

public struct LandmarkLocation {
  public var latitude: Float
  public var longitude: Float
}

然后,C++ 代码可以调用 getLatitudegetLongitude 成员函数来访问存储的属性值。

从 C++ 中使用 Swift 标准库类型

C++ 中可以使用几种 Swift 标准库类型。本节介绍如何在 C++ 中使用受支持的 Swift 标准库类型

在 C++ 中使用 Swift String

Swift 的 String 类型暴露给 C++。它可以直接使用 C++ 中的字符串字面量进行初始化

#include <SwiftLibrary-Swift.h>

void createSwiftString() {
  swift::String test = "Hello Swift world!";
}

C++ std::string 可以轻松转换为 Swift String

void callSwiftAPI(const std::string &stringValue) {
  SwiftLibrary::functionTakesString(swift::String(stringValue));
}

同样的转换也可以反向进行,从 Swift String 转换为 C++ std::string

std::string getStringFromSwift() {
  return (std::string)SwiftLibrary::giveMeASwiftString();
}

String 的 C++ 表示形式提供了对 String 的许多方法和属性的访问,包括

此处未列出的其他几种方法和属性也可用。

在 Objective-C++ 语言模式下,Objective-C NSString * 可以与 Swift String 相互转换。

在 C++ 中使用 Swift Array

Swift 的 Array 类型暴露给 C++。C++ 使用 swift::Array 类模板来表示它。它必须使用表示 Swift 类型的 C++ 类型来实例化。当 Swift 中使用此类类型的 Swift Array 并通过公共 Swift API 暴露回 C++ 时,它也可以使用原生 C++ 类型实例化。

Swift Array 可以使用 C++ 中的 for 循环遍历。例如,以下面的 StringsAndNumbers Swift 模块接口为例

// Swift module 'StringsAndNumbers'

public func findTheStrings() -> [String]
public func processRandomNumbers(_ numbers: [Float])

C++ for 循环可以遍历从 findTheStrings 返回的 Array

#include <StringsAndNumbers-Swift.h>

void printTheFoundStrings() {
  auto stringsArray = StringsAndNumbers::findTheStrings();
  for (const auto &swiftString: stringsArray) {
    std::cout << (std::string)swiftString << ", ";
  }
}

可以在 C++ 中创建一个空的 Array,对其进行修改,然后将其传递给 Swift。例如,C++ 代码可以创建一个包含一些浮点数的 Array,并将其传递给 processRandomNumbers

#include <StringsAndNumbers-Swift.h>

void processSomeTrulyUniqueRandomNumbers() {
  auto array = swift::Array<float>::init();
  array.append(1.0f);
  array.append(42.0f);
  StringsAndNumbers::processRandomNumbers(array);
}

可以使用 C++ 中的 operator [] 访问数组中的单个元素。但是,您不能使用 operator [] 修改数组元素。C++ 尚不支持修改 Swift Array 中的单个元素。

Array 的 C++ 表示形式提供了对 Array 的许多方法和属性的访问,包括

此处未列出的其他几种方法和属性也可用。

在 C++ 中使用 Swift Optional

Swift 的 Optional 类型暴露给 C++。C++ 使用 swift::Optional 类模板来表示它。它必须使用表示 Swift 类型的 C++ 类型来实例化。当 Swift 中使用此类类型的 Swift Array 并通过公共 Swift API 暴露回 C++ 时,它也可以使用原生 C++ 类型实例化。

可以使用 get 成员函数提取存储在 Swift Optional 中的值。例如,以下面的 OptionalValues Swift 模块接口为例

// Swift module 'OptionalValues'

public func maybeMakeString() -> String?
public func callMeOnThePhoneMaybe(_ number: UInt64?)

C++ get 成员函数可用于提取 maybeMakeString 返回的 String

#include <OptionalValues-Swift.h>

void printAStringOrNone() {
  auto maybeString = OptionalValues::maybeMakeString();
  if (maybeString) {
    std::cout << (std::string)maybeString.get() << "\n";
  } else {
    std::cout << "Got no value from Swift :( \n";
  }
}

在 C++ 中,Optional 可以隐式转换为 bool。这允许您在 if 条件中检查它是否具有值,如上面示例所示。

Swift Optional 也可以从 C++ 构建,使用 somenone case 构造函数

#include <OptionalValues-Swift.h>

void callMeOnThePhone() {
  OptionalValues::callMeOnThePhoneMaybe(
    swift::Optional<uint64_t>::some(1234567890));
}

附录

本节包含其他表格和参考资料,用于说明上述文档中概述的某些主题。

<swift/bridging> 中的自定义宏列表

文档
SWIFT_NAME 在 Swift 中重命名 C++ API
SWIFT_COMPUTED_PROPERTY 将 Getter 和 Setter 映射到计算属性
SWIFT_CONFORMS_TO_PROTOCOL 使类模板符合 Swift 协议
SWIFT_IMMORTAL_REFERENCE 永久引用类型
SWIFT_SHARED_REFERENCE 共享引用类型
SWIFT_UNSAFE_REFERENCE 不安全引用类型
SWIFT_RETURNS_INDEPENDENT_VALUE 注解返回独立引用或视图的方法
SWIFT_MUTATING 常量成员函数不得修改对象
SWIFT_NONCOPYABLE C++ 结构体和类默认是值类型
SWIFT_SELF_CONTAINED 将 C++ 结构体或类注解为自包含

文档修订历史

本节列出了对本参考指南所做的最新更改。

2024-08-12

2024-06-11

2024-03-26

2023-06-05