专注、坚持

[译] 在 Objective-C API 中指定可空性

2019.05.09 by kingcos
作者 原文链接
Apple Developer Designating Nullability in Objective-C APIs

使用可空性(Nullability)标志或标志一块区域,以控制将 Objective-C 声明导入 Swift 中的方式。


概览

在 Objective-C 中,常用可以为空的指针来和对象的引用打交道,这(个空)就是 Objective-C 中的 nil。在 Swift 中,所有值——包括对象实例——都被确保为非空(Non-null)。取而代之,表示一个可能丢失的值可以将其包裹在可选(Optional)类型中。当我们需要表示一个值的丢失,可以使用 nil 值。

我们可以在 Objective-C 的代码中标示声明,以指示一个实例是否可以有空(Null)或 nil 值。这些标志改变了 Swift 如何导入声明(的方式)。举个 Swift 如何导入未标示声明的例子,如下:

@interface MyList : NSObject
- (MyListItem *)itemWithName:(NSString *)name;
- (NSString *)nameForItem:(MyListItem *)item;
@property (copy) NSArray<MyListItem *> *allItems;
@end

Swift 导入了每个对象实例的参数、返回值、以及作为隐式包裹可选的属性(译者注:隐式包裹即 Type!,表示该值被保证为非 nil,即可在后续使用时省略 ? 解包,但当该值为 nil 却没有使用可选解包时,程序将发生崩溃,另可详见《Objective-C 与 Swift 桥接中的坑 》一文)。

class MyList: NSObject {
    func item(withName name: String!) -> MyListItem!
    func name(for item: MyListItem!) -> String!
    var allItems: [MyListItem]!
}

为单独的声明标记可空性

在 Objective-C 代码中使用可空性标志可以指定参数类型、属性类型、或者返回值类型是否为可空的。标示属性声明、参数类型、以及返回值类型这些简单对象或者 Block 指针,可以使用 nullablenonnull、以及 null_resettable 属性标志。如果一个类型没有提供可空性信息,Swift 将不区分可选和非可选引用,并将该类型作为隐式可选解包导入。

下表描述了 Swift 如何导入带有不同可空性标志的类型:

  • 非空(Nonnullable)——无论是直接标示抑或包含在一个标示区域内,都作为非可选(类型)导入
  • 可空(Nullable)——作为可选(类型)导入
  • 不带有可空性标示或 null_resettable 标志——作为隐式解包可选(类型)导入

下面的代码展示了标示后的 MyList 类型。两个方法的返回值被标示为 nullable,因为如果列表不包含给定的列表项或名称,方法将返回 nil。所有其他对象实例均被标示为 nonnull

@interface MyList : NSObject
- (nullable MyListItem *)itemWithName:(nonnull NSString *)name;
- (nullable NSString *)nameForItem:(nonnull MyListItem *)item;
@property (copy, nonnull) NSArray<MyListItem *> *allItems;
@end

通过这些标志,Swift 将不使用任何隐式包裹可选来导入 MyList 类型:

class MyList: NSObject {
    func item(withName name: String) -> MyListItem?
    func name(for item: MyListItem) -> String?
    var allItems: [MyListItem]
}

nullablenonnull 标志是 _Nullable_Nonnull 的简化版。当对指针类型使用 const 关键字时,我们可以在绝大多数上下文中使用它们。复杂的指针类型,比如 id * 必须明确标示所使用的标志。举个例子,一个指向可空对象引用的不可空的指针,声明为 _Nullable id * _Nonnull

标示区域不可空

通过标示整个区域为可空性检查可以简化标示 Objective-C 代码的过程。标记 NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END 之间区域内的代码,只需要标示可空类型的声明。未标记声明的部分将被当作是非空的。

MyList 声明标记为可空性检查降低了所需要的标志数量。Swift 将以与上一节相同的方式导入该类型。

NS_ASSUME_NONNULL_BEGIN

@interface MyList : NSObject
- (nullable MyListItem *)itemWithName:(NSString *)name;
- (nullable NSString *)nameForItem:(MyListItem *)item;
@property (copy) NSArray<MyListItem *> *allItems;
@end

NS_ASSUME_NONNULL_END

注意,尽管在检查区域内,但typedef 的类型不会被假定为非空,因为它们内在并非是可空的。

参阅

定制 Objective-C API

为 Swift 重命名 Objective-C API

使用 NS_SWIFT_NAME 宏为 Swift 定制 API 名称。

为 Swift 改进 Objective-C 声明

使用 NS_REFINED_FOR_SWIFT 宏以改变 API 是如何导入 Swift 中的。

在 Objective-C 类型中添加宏以在 Swift 中组合他们的值。

在 Objective-C 中标记 API 可用性

使用宏以表示 Objective-C API 的可用性。

让 Objective-C API 在 Swift 中不可用

使用 NS_SWIFT_UNAVAILABLE 宏以在 Swift 中禁用 API。


译者注:

  1. API 即通用意义上的接口,不再翻译;
  2. 由于 null 和 nil 含义类似,均为空,在本文中为表区分,将 null 翻译为「空」,nil 不作翻译,代码中不会进行任何翻译;
  3. 由于英文语法规范,通常会有主语 You,我可能会在不改变核心意义的情况下,会忽略人称,在部分情况将其改为第一人称「我们」。

Reference