Swift 和 C++ 混合使用
目录
- 简介
- 概览
- 在 Swift 中使用 C++ 类型和函数
- 自定义 C++ 映射到 Swift 的方式
- 在 Swift 中扩展 C++ 类型
- 使用 C++ 容器
- 将 C++ 类型映射到 Swift 引用类型
- 从 Swift 使用 C++ 标准库
- 在 Swift 中使用 C++ 引用和视图类型
- 从 C++ 访问 Swift API
- 从 C++ 使用 Swift 类型和函数
- 从 C++ 使用 Swift 标准库类型
- 附录
- 文档修订历史
简介
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.h
和 tree.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++ 的 using
或 typedef
声明会变成 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++ 值一起使用。
- 协议类型 可以表示符合协议的 C++ 值。
例如,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
注释使所有特化(如 SerializedInt
和 SerializedFloat
)在 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++ 容器类型符合以下协议
- 允许在 Swift 中使用标准 Swift API 安全访问底层容器。
- 提供一种将 C++ 容器转换为 Swift 集合类型的方法。
这些协议及其一致性规则在下面描述。使用符合这些协议的 C++ 容器的推荐方法在 后续章节 中进行了总结。
一些 C++ 容器是 Swift 集合
Swift 使提供对其元素随机访问的 C++ 容器(如 std::vector
)自动遵循 Swift 的 RandomAccessCollection
协议。例如,此函数返回的 std::vector
容器由 Swift 自动遵循 RandomAccessCollection
协议。
std::vector<Tree> getEnchantedTrees();
遵循 RandomAccessCollection
协议使得在 Swift 中安全地遍历容器的元素成为可能,可以使用熟悉的控制流语句(如 for-in
循环)。集合方法(如 map
和 filter
)也可用。
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 最有可能对容器进行深拷贝:
- 容器在
for-in
循环中使用时。 - 容器与诸如
filter
和reduce
等方法一起使用时,这些方法的实现来自 Swift 的Sequence
协议。
随机访问 C++ 集合的一致性规则
为了使 C++ 容器类型在 Swift 中自动遵循 RandomAccessCollection
协议,必须满足以下两个条件:
- C++ 容器类型必须具有
begin
和end
成员函数。这两个函数都必须是常量,并且必须返回相同的迭代器类型。 - C++ 迭代器类型必须满足
RandomAccessIterator
C++ 要求。必须可以使用 C++ 中的operator +=
来推进它,并且还可以使用 C++ 中的operator []
对其进行下标访问。 - C++ 迭代器类型必须可以使用
operator ==
进行比较。
当满足这些条件时,Swift 会使表示底层 C++ 容器类型的 Swift 结构遵循 CxxRandomAccessCollection
协议,从而添加 RandomAccessCollection
一致性。
C++ 容器可以转换为 Swift 集合
不提供对其元素随机访问的顺序 C++ 容器类型在 Swift 中自动遵循 CxxConvertibleToCollection
协议。例如,此函数返回的 std::set
容器由 Swift 自动遵循 CxxConvertibleToCollection
协议。
std::set<int> getWinningNumers();
遵循 CxxConvertibleToCollection
协议使得轻松将 C++ 容器转换为 Swift 集合类型(如 Array
或 Set
)成为可能。例如,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
协议,必须满足以下两个条件:
- C++ 容器类型必须具有
begin
和end
成员函数。这两个函数都必须是常量,并且必须返回相同的迭代器类型。 - C++ 迭代器类型必须满足
InputIterator
C++ 要求。必须可以使用 C++ 中的operator ++
来递增它,并且还可以使用 C++ 中的operator *
对其进行解引用。 - C++ 迭代器类型必须可以使用
operator ==
进行比较。
在 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
一致性。
使用 C++ 容器的推荐方法
以下摘要概述了当前推荐的在 Swift 中使用 C++ 容器的方法:
- 使用
for-in
循环遍历遵循RandomAccessCollection
协议的 C++ 容器。 - 在使用遵循
RandomAccessCollection
协议的 C++ 容器时,使用集合 API(如map
或filter
)。 - 使用下标运算符访问遵循
RandomAccessCollection
协议的 C++ 容器中的特定元素。 - 如果您想遍历其他顺序容器的元素,或者如果您想使用集合 API(如
map
或filter
),请将它们转换为 Swift 集合。 - 在关联 C++ 容器中查找值时,使用来自
CxxDictionary
协议的下标运算符。
在性能敏感的 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++ 容器销毁后,很容易使用迭代器。
- 很容易解引用已超过容器最后一个元素的迭代器。
在使用 C++ 容器时,您应该使用诸如 CxxRandomAccessCollection
、CxxConvertibleToCollection
和 CxxDictionary
等协议,而不是依赖 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_ptr
或 std::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++ 中传递的引用计数类型。它们通常使用以下两种方式之一:
- 自定义 retain 和 release 操作,这些操作会递增和递减存储在对象内部的引用计数。
- 或者,非侵入式共享指针类型,如
std::shared_ptr
,可以将引用计数存储在对象外部。
目前,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++ 引用
- 原始指针
- 没有用户定义的复制构造函数的 C++ 类或结构体,其中包含类型在此列表中的字段(递归地)。
例如,以下两个 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++ 成员函数返回完全独立的引用或视图。 那么此类成员函数将被假定为安全的。
- 假定特定的 C++ 类或结构体是自包含的。 那么所有返回此类自包含类型的成员函数都将被假定为安全的。
注释返回独立引用或视图的方法
可以向 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 枚举,该枚举在 C++ 中设置为特定的 case 值。
- 在 C++ 中使用
switch
语句切换 Swift 枚举。
例如,以下 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 结构体和枚举具有 mutating
和 nonmutating
方法。 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++ 代码可以调用 getLatitude
和 getLongitude
成员函数来访问存储的属性值。
从 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 的许多方法和属性的访问,包括
isEmpty
getCount
lowercased
和uppercased
hasPrefix
和hasSuffix
append
此处未列出的其他几种方法和属性也可用。
在 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 的许多方法和属性的访问,包括
getCount
getCapacity
append
insertAt
removeAt
此处未列出的其他几种方法和属性也可用。
在 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++ 构建,使用 some
或 none
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
- 从
<swift/bridging>
中添加了几个自定义宏到列表中。
2024-06-11
- 非可复制的 C++ 类型现在在 Swift 中可用。
2024-03-26
- 更新了 Swift 中 C++ 模板运算符支持的状态。
2023-06-05
- 发布了指南的初始版本,描述了如何混合使用 Swift 和 C++。