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,还需要指定SwiftReleaseOpSwiftRetainOp)。

对于方法,可能的值是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::

用于属性和全局变量。有四个选项,由它们的缩写标识

  • NonnullN(对应于_Nonnull

  • OptionalO(对应于_Nullable

  • UnspecifiedU(对应于_Null_unspecified

  • ScalarS(已弃用)

注意,即使在“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_ENUMNS_EXTENSIBLE_STRING_ENUM。有三个选项

  • “struct”(可扩展)

  • “enum”

  • “none”

注意,即使是“enum”包装器在 Swift 中仍然被呈现为结构体;它只是一个“更像枚举”的结构体。

- Name: AVMediaType
  SwiftWrapper: none
EnumKind::

具有与NS_ENUMNS_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