API 说明:不修改头文件进行注解¶
问题: 你需要使用一些头文件,但你希望添加一些额外信息到 API 中。你不想将这些信息直接放在头文件中 - 这可能是因为你想要保持头文件的整洁,以便其他客户端使用,或者可能是因为这些头文件来自一些开源项目,你不想对其进行任何修改。
不完整的解决方案: 在你自己的头文件中重新声明 API 的所有有趣部分,并添加你想要的属性。不幸的是,这种方法:
对必须在定义中存在的属性不起作用
不允许以其他方式更改定义
需要在所有客户端代码中包含你的头文件才能生效
更好的解决方案: 提供一个包含你想要添加的信息的“侧边车”文件,并让编译器的模块构建逻辑自动读取该文件。
这就是 API 说明的作用。
API 说明使用基于 YAML 的文件格式。YAML 是一种最好通过示例来解释的格式,因此这里有一个来自编译器测试套件的小型示例,展示了针对假设的“SomeKit”框架的 API 说明。
用法¶
API 说明文件相对于定义模块的模块映射,以“SomeKit.apinotes”命名,其中“SomeKit”是模块名称。此外,名为“SomeKit_private.apinotes”的文件也将被拾取,用于私有模块映射。对于裸模块,这两个文件将与相应的模块映射位于同一目录下;对于框架模块,它们应该分别放在 Headers 和 PrivateHeaders 目录中。私有顶级框架模块的模块映射也应该放在 PrivateHeaders 目录中,尽管它不需要在名称上添加额外的“_private”后缀。
只有在传递了-fapi-notes-modules
选项时,Clang 才会在模块映射旁边搜索 API 说明文件。
限制¶
由于它们是通过模块名称识别的,因此 API 说明无法用于修改任意文本头文件。
“版本化”API 说明¶
许多 API 说明会影响 C API 如何导入到 Swift 中。为了在保持向后兼容性的同时改变这种行为,可以根据提供给编译器的 Swift 兼容版本(例如-fapi-notes-swift-version=5
)选择性地应用 API 说明。规则是,显式版本化的 API 说明适用于该版本以及所有早期版本,并且任何适用的显式版本化 API 说明优先于非版本化 API 说明。
参考¶
API 说明文件包含一个 YAML 字典,其中包含以下顶级条目
- 名称::
模块的名称(对于框架,即框架名称)。注意,即使在私有 API 说明文件中,这始终是顶级模块的名称。
Name: MyFramework
- 类、协议、标签、类型定义、全局变量、枚举、函数、命名空间::
顶级声明的数组。数组中的每个条目都必须有一个包含其 Objective-C 或 C++ 名称的“名称”键。“标签”指的是结构体、C++ 类、枚举和联合体;“类”指的是 Objective-C 类;“枚举”指的是枚举的成员。
Classes: - Name: MyController … - Name: MyView …
- SwiftVersions::
包含用于向后兼容性的显式信息。数组中的每个条目都包含一个“版本”键,该键应该设置为“4”,以表示仅适用于 Swift 4 模式及更早版本的注解。此字典中的其他条目与顶级条目中的声明条目相同:类、协议、标签、类型定义、全局变量、枚举和函数。
SwiftVersions: - Version: 4 Classes: … Protocols: …
“类”和“协议”下的每个条目都可以包含“方法”和“属性”数组,以及下面描述的属性
- 方法::
由“选择器”和“方法类型”标识;方法类型是“实例”或“类”。
Classes: - Name: UIViewController Methods: - Selector: "presentViewController:animated:" MethodKind: Instance …
- 属性::
由“名称”和“属性类型”标识;属性类型也是“实例”或“类”。
Classes: - Name: UIView Properties: - Name: subviews PropertyKind: Instance …
每个声明都支持以下注解(如果与该声明类型相关),所有这些注解都是可选的
- SwiftName::
等效于
NS_SWIFT_NAME
。对于方法,必须包含所有参数的完整 Swift 名称。使用“_”省略参数标签。- Selector: "presentViewController:animated:" MethodKind: Instance SwiftName: "present(_:animated:)" - Class: NSBundle SwiftName: Bundle
- SwiftImportAs::
对于类,可能的值是
owned
(等效于SWIFT_SELF_CONTAINED
)或reference
(等效于SWIFT_SHARED_REFERENCE
,还需要指定SwiftReleaseOp
和SwiftRetainOp
)。对于方法,可能的值是
unsafe
(等效于SWIFT_RETURNS_INDEPENDENT_VALUE
)或computed_property
(等效于SWIFT_COMPUTED_PROPERTY
)。Tags: - Name: RefCountedStorage SwiftImportAs: reference SwiftReleaseOp: RCRelease SwiftRetainOp: RCRetain
- SwiftCopyable::
允许在 Swift 中将 C++ 类注释为不可复制的。等效于
SWIFT_NONCOPYABLE
,或者显式符合: ~Copyable
。Tags: - Name: tzdb SwiftCopyable: false
- SwiftConformsTo::
允许在 Swift 中将 C++ 类注释为符合 Swift 协议。等效于
SWIFT_CONFORMS_TO_PROTOCOL
。该值是 Swift 协议的模块限定名称。Tags: - Name: vector SwiftConformsTo: Cxx.CxxSequence
- Availability, AvailabilityMsg::
“nonswift”的值等效于
NS_SWIFT_UNAVAILABLE
。“available”的值可以在“SwiftVersions”部分中使用,以撤消“nonswift”的效果。- Selector: "dealloc" MethodKind: Instance Availability: nonswift AvailabilityMsg: "prefer 'deinit'"
- SwiftPrivate::
等效于 NS_REFINED_FOR_SWIFT。
- Name: CGColorEqualToColor SwiftPrivate: true
- Nullability::
用于属性和全局变量。有四个选项,由它们的缩写标识
Nonnull
或N
(对应于_Nonnull
)Optional
或O
(对应于_Nullable
)Unspecified
或U
(对应于_Null_unspecified
)Scalar
或S
(已弃用)
注意,即使在“SwiftVersions”部分中,“Nullability”也会被“Type”覆盖。
注意
‘Nullability’也可以用来描述方法和函数的参数类型,但这种用法已被‘Parameters’(见下文)弃用。
- Name: dataSource Nullability: O
- NullabilityOfRet::
用于方法和函数。描述返回类型的可空性。
注意,即使在“SwiftVersions”部分中,“NullabilityOfRet”也会被“ResultType”覆盖。
警告
由于编译器错误,‘NullabilityOfRet’ 也可能更改参数的可空性 (rdar://30544062)。避免使用它,而是使用‘ResultType’ 并指定返回类型以及可空性注解(参见‘ResultType’ 的文档)。
- Selector: superclass MethodKind: Class NullabilityOfRet: O
- Type::
用于属性和全局变量。这将完全覆盖声明的类型;它理想情况下只应用于 Swift 向后兼容性,当现有的类型信息在头文件中变得更加精确时。在可能的情况下,优先使用‘Nullability’和其他注解。
我们解析指定的类型,就好像它出现在正在修改其类型的声明的位置一样。宏不可用,可空性必须显式应用(即使在
NS_ASSUME_NONNULL_BEGIN
部分中)。- Name: delegate PropertyKind: Instance Type: "id"
- ResultType::
用于方法和函数。这将完全覆盖返回类型;它理想情况下只应用于 Swift 向后兼容性,当现有的类型信息在头文件中变得更加精确时。
我们解析指定的类型,就好像它出现在正在修改其类型的声明的位置一样。宏不可用,可空性必须显式应用(即使在
NS_ASSUME_NONNULL_BEGIN
部分中)。- Selector: "subviews" MethodKind: Instance ResultType: "NSArray * _Nonnull"
- SwiftImportAsAccessors::
用于属性。如果为真,则属性将在 Swift 中作为其访问器方法公开,而不是作为使用
var
的计算属性。- Name: currentContext PropertyKind: Class SwiftImportAsAccessors: true
- NSErrorDomain::
用于
NSError
代码枚举。该值是关联的域NSString
常量的名称;空字符串 (""
) 表示枚举是普通的枚举,而不是错误代码。- Name: MKErrorCode NSErrorDomain: MKErrorDomain
- SwiftWrapper::
控制
NS_STRING_ENUM
和NS_EXTENSIBLE_STRING_ENUM
。有三个选项“struct”(可扩展)
“enum”
“none”
注意,即使是“enum”包装器在 Swift 中仍然被呈现为结构体;它只是一个“更像枚举”的结构体。
- Name: AVMediaType SwiftWrapper: none
- EnumKind::
具有与
NS_ENUM
和NS_OPTIONS
相同的效果。有四个选项“NSEnum” / “CFEnum”
“NSClosedEnum” / “CFClosedEnum”
“NSOptions” / “CFOptions”
“none”
- Name: GKPhotoSize EnumKind: none
- Parameters::
用于方法和函数。参数由 0 为基的“位置”标识,并支持“Nullability”、“NoEscape”和“Type”键。
注意
在参数条目中使用‘Parameters’ 来描述块的参数尚未实现。而是对整个参数使用‘Type’。
- Selector: "isEqual:" MethodKind: Instance Parameters: - Position: 0 Nullability: O
- NoEscape::
仅用于块参数。等效于
NS_NOESCAPE
。- Name: dispatch_sync Parameters: - Position: 0 NoEscape: true
- SwiftBridge::
用于桥接到 Swift 值类型的 Objective-C 类类型。空字符串 (“”) 表示类型未桥接。在 Apple 框架之外不支持(它的 Swift 端需要符合实现细节协议,这些协议可能会发生变化)。
- Name: NSIndexSet SwiftBridge: IndexSet
- DesignatedInit::
用于 init 方法。等效于
NS_DESIGNATED_INITIALIZER
。- Selector: "initWithFrame:" MethodKind: Instance DesignatedInit: true