在 Objective-C API 中指定可空性

79 阅读3分钟

使用可空性注解或将区域标记为已注解,以控制将 Objective-C 声明导入 Swift 的方式。

概览

Objective-C 中,你可以使用可以为 null 的指针来处理对对象的引用,在 Objective-C 中称为 nil。在 Swift 中,所有值——包括对象实例——都保证是非空的。相反,你表示一个可能会丢失的值,因为它包装在一个可选类型中。当你需要指示缺少某个值时,可以使用值 nil

你可以在 Objective-C 代码中注释声明以指示实例是否可以具有 nullnil 值。这些注释改变了 Swift 导入声明的方式。有关 Swift 如何导入未注释声明的示例,请考虑以下代码:

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

Swift 将每个对象实例参数、返回值和属性作为隐式解包的可选类型导入:

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

标注单个声明的可空性

你可以在 Objective-C 代码中使用可空性注解来指定参数类型、属性类型或返回类型是否可为空。使用 nullablenonnullnull_resettable 属性来注释作为简单对象或 Block 指针的属性声明、参数类型和返回类型。如果没有为类型提供可空性信息,Swift 不会区分可选引用和非可选引用,并将该类型导入为隐式展开的可选类型。

此列表描述了 Swift 如何导入具有不同可空性注释的类型:

  • Nonnullable——作为非可选类型导入,无论是直接注解还是通过包含在注解区域中。
  • Nullable——作为可选类型导入。
  • 没有可空性注解或带有 null_resettable 注解——作为隐式解包的可选值导入。

以下代码显示了注解后的 MyList 类型。这两个方法的返回类型被标注为可为空,因为如果列表不包含给定的列表项或名称,则方法返回 nil。所有其他对象实例都被注解为非空。

@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

将区域注释为 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 类型不假定为 nonnull,即使在宏包裹区域内也是如此,因为它们本质上不是可以为空的。