使用 macOS API¶
随着每个新 macOS 版本的发布,都会添加新的 API。由于 Firefox 运行在各种平台上,并且由于我们支持使用各种 SDK 进行构建,因此在 Firefox 中使用 macOS API 需要格外小心。
API 的可用性和运行时检查¶
首先,如果您使用的是所有 Firefox 运行的 macOS 版本(即 10.15 及更高版本)都支持的 API,那么您无需担心任何事情:API 声明将存在于所有支持的 SDK 中,并且您无需进行任何运行时检查。
如果您想使用在 10.15 之后添加的 macOS API,则必须进行运行时检查。此要求与用于构建的 SDK 完全无关。
运行时检查应具有以下形式(将11.0
替换为适当的版本)
if (@available(macOS 11.0, *)) {
// Code for macOS 11.0 or later
} else {
// Code for versions earlier than 11.0.
}
@available
保护符可用于 Objective-C(++) 代码。(在 C++ 代码中,您可以使用这些nsCocoaFeatures
方法。)
对于每个 API,SDK 头文件中的 API 声明都用API_AVAILABLE
宏进行注释。例如,NSVisualEffectMaterial
枚举的定义如下所示
typedef NS_ENUM(NSInteger, NSVisualEffectMaterial) {
NSVisualEffectMaterialTitlebar = 3,
NSVisualEffectMaterialSelection = 4,
NSVisualEffectMaterialMenu API_AVAILABLE(macos(10.11)) = 5,
// [...]
NSVisualEffectMaterialSheet API_AVAILABLE(macos(10.14)) = 11,
// [...]
} API_AVAILABLE(macos(10.10));
编译器理解这些注释,并确保您将所有注释 API 的用法包装在适当的@available
运行时检查中。
框架¶
在某些极少数情况下,您需要来自并非所有受支持的 macOS 版本都可用的框架的功能。例如,Metal.framework
(在 10.11 中添加)和MediaPlayer.framework
(在 10.12.2 中添加)。
在这种情况下,您可以选择在运行时dlopen
您的框架(就像我们对 MediaPlayer 所做的那样),或者您可以使用-weak_framework
就像我们对 Metal 所做的那样
if CONFIG['OS_ARCH'] == 'Darwin':
OS_LIBS += [
# Link to Metal as required by the Metal gfx-hal backend
'-weak_framework Metal',
]
使用新 API 与旧 SDK¶
如果您想使用在 10.15 之后引入的 API,您现在需要多注意一件事。除了上一节中描述的运行时检查之外,您还必须跳过额外的障碍才能使构建成功,因为为了使 Firefox 能够在 macOS 10.15 及更高版本上运行,Firefox 的构建目标必须保持在 10.15。
为了使编译器接受您的代码,您需要将一些 API 声明复制到您自己的代码中。从您能找到的最新 SDK 中复制它。确切的步骤因 API 类型(枚举、objc 类、方法等)而异,但总体方法如下所示
#if !defined(MAC_OS_VERSION_12_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_12_0
@interface NSScreen (NSScreen12_0)
// https://developer.apple.com/documentation/appkit/nsscreen/3882821-safeareainsets?language=objc&changes=latest_major
@property(readonly) NSEdgeInsets safeAreaInsets;
@end
#endif
有关MAC_OS_X_VERSION_MAX_ALLOWED
宏的更多信息,请参阅支持多个 SDK 文档。
请牢记以下三点
仅复制您需要的内容。
将您的声明包装在
MAC_OS_X_VERSION_MAX_ALLOWED
检查中,以便如果使用已经包含这些声明的 SDK,您的声明不会与 SDK 中的声明冲突。包含
API_AVAILABLE
注释,以便编译器可以防止您意外地在不受支持的 macOS 版本上调用 API。
我们当前的代码并不总是遵循API_AVAILABLE
建议,但应该这样做。
枚举类型和 C 结构体¶
如果您需要新的枚举类型或 C 结构体,请复制整个类型声明并将其包装在适当的 ifdefs 中。示例
#if !defined(MAC_OS_X_VERSION_10_12_2) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12_2
typedef NS_ENUM(NSUInteger, MPNowPlayingPlaybackState) {
MPNowPlayingPlaybackStateUnknown = 0,
MPNowPlayingPlaybackStatePlaying,
MPNowPlayingPlaybackStatePaused,
MPNowPlayingPlaybackStateStopped,
MPNowPlayingPlaybackStateInterrupted
} MP_API(ios(11.0), tvos(11.0), macos(10.12.2), watchos(5.0));
#endif
现有枚举类型的新的枚举值¶
如果枚举类型本身已存在,但获得了新的值,请在未命名的枚举中定义该值
#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
enum { NSVisualEffectMaterialSelection = 4 };
#endif
(这是一个有趣的案例示例:NSVisualEffectMaterialSelection
从 macOS 10.10 开始可用,但仅在从 10.12 SDK 开始的 SDK 中定义。)
Objective-C 类¶
对于新的 Objective-C 类,请复制整个@interface
声明并将其包装在适当的 ifdefs 中。
我个人没有测试过这一点。如果这无法编译(或者可能链接?),您可以使用以下解决方法
将您的方法和属性定义为
NSObject
上的类别。使用
NSClassFromString()
在运行时查找类。如果您需要创建子类,请使用
objc_allocateClassPair
和class_addMethod
在运行时进行。 这是一个示例。
现有类上的 Objective-C 属性和方法¶
如果已存在的 Objective-C 类获得了新的方法或属性,您可以借助类别将其“添加到”现有类声明中
@interface ExistingClass (YourMadeUpCategoryName)
// methods and properties here
@end
函数¶
对于独立函数,我不完全确定该怎么做。理论上,从新的 SDK 头文件中复制声明应该可以工作。示例
extern "C" {
__attribute__((warn_unused_result)) bool
SecTrustEvaluateWithError(SecTrustRef trust, CFErrorRef _Nullable * _Nullable CF_RETURNS_RETAINED error)
API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0));
__nullable
CFDataRef SecCertificateCopyNormalizedSubjectSequence(SecCertificateRef certificate)
__OSX_AVAILABLE_STARTING(__MAC_10_12_4, __IPHONE_10_3);
}
我不确定链接器或动态链接器在符号不可用时会做什么。这是否需要__attribute__((weak_import))
注释?
也许 SDK 中的 .tbd 文件的作用就在于此?以便链接器知道允许哪些符号?因此,复制头文件中的代码无法解决该部分问题。
无论如何,始终有效的方法是纯粹的运行时方法
为所需的函数定义类型,但不要定义函数本身。
在运行时,使用
dlsym
查找函数。
关于 Rust 的说明¶
如果您从 Rust 代码调用 macOS API,那么您基本上需要自行解决。Apple 没有提供任何 Rust “头文件”,因此实际上没有可供参考的 SDK。因此,无论用于构建的 SDK 是什么,您都必须自己提供 API 声明。
从某种程度上说,您可以避开一些构建时的问题。您无需担心任何#ifdefs
,因为没有可能与之冲突的系统头文件。
另一方面,您仍然需要担心运行时 API 的可用性。在 Rust 中,API 声明上没有可用性属性,也没有@available
运行时检查帮助程序,并且如果在可用性检查之外调用 API,编译器不会发出警告。