166

在 Xcode 6.3 中引入了新的注解,以更好地表达Objective-C中 API 的意图(当然也确保更好的 Swift 支持)。那些注释当然是nonnull,nullablenull_unspecified.

但是在 Xcode 7 中,出现了很多警告,例如:

指针缺少可空性类型说明符(_Nonnull、_Nullable 或 _Null_unspecified)。

除此之外,Apple 使用另一种类型的可空性说明符,标记它们的 C 代码(源代码):

CFArrayRef __nonnull CFArrayCreate(
CFAllocatorRef __nullable allocator, const void * __nonnull * __nullable values, CFIndex numValues, const CFArrayCallBacks * __nullable callBacks);

所以,总而言之,我们现在有这 3 个不同的可空性注释:

  • nonnull, nullable,null_unspecified
  • _Nonnull, _Nullable,_Null_unspecified
  • __nonnull, __nullable,__null_unspecified

即使我知道为什么以及在哪里使用哪个注释,我还是对我应该使用哪种类型的注释、在哪里以及为什么使用感到有些困惑。这是我可以收集到的:

  • 对于属性,我应该使用nonnull, nullable, null_unspecified.
  • 对于方法参数,我应该使用nonnull, nullable, null_unspecified.
  • 对于 C 方法,我应该使用__nonnull, __nullable, __null_unspecified.
  • 对于其他情况,例如双指针,我应该使用_Nonnull, _Nullable, _Null_unspecified.

但是我仍然对为什么我们有这么多基本上做同样事情的注释感到困惑。

所以我的问题是:

这些注释之间的确切区别是什么,如何正确放置它们以及为什么?

4

4 回答 4

173

clang 文档中:

可空性(类型)限定符表示给定指针类型的值是否可以为 null(_Nullable限定符),对 null 没有定义的含义(_Nonnull限定符),或者 null 的用途不明确(_Null_unspecified限定符) . 因为可空性限定符在类型系统中表示,所以它们比nonnullandreturns_nonnull属性更通用,允许(例如)表示非空指针数组的可空指针。可空性限定符被写入它们所应用的指针的右侧。

, 和

在 Objective-C 中,可以在 Objective-C 方法和属性中使用上下文相关、非下划线关键字的可空性限定符有另一种拼写

因此,对于方法返回和参数,您可以使用双下划线版本__nonnull/ __nullable/__null_unspecified代替单下划线版本,或代替非下划线版本。区别在于单下划线和双下划线需要放在类型定义之后,而非下划线需要放在类型定义之前。

因此,以下声明是等价的并且是正确的:

- (nullable NSNumber *)result
- (NSNumber * __nullable)result
- (NSNumber * _Nullable)result

对于参数:

- (void)doSomethingWithString:(nullable NSString *)str
- (void)doSomethingWithString:(NSString * _Nullable)str
- (void)doSomethingWithString:(NSString * __nullable)str

对于属性:

@property(nullable) NSNumber *status
@property NSNumber *__nullable status
@property NSNumber * _Nullable status

然而,当涉及到返回不同于 void 的双指针或块时,事情会变得复杂,因为这里不允许使用非下划线:

- (void)compute:(NSError *  _Nullable * _Nullable)error
- (void)compute:(NSError *  __nullable * _Null_unspecified)error;
// and all other combinations

与接受块作为参数的方法类似,请注意nonnull/nullable限定符适用于块,而不是它的返回类型,因此以下是等价的:

- (void)executeWithCompletion:(nullable void (^)())handler
- (void)executeWithCompletion:(void (^ _Nullable)())handler
- (void)executeWithCompletion:(void (^ __nullable)())handler

如果块有返回值,那么您将被迫进入下划线版本之一:

- (void)convertObject:(nullable id __nonnull (^)(nullable id obj))handler
- (void)convertObject:(id __nonnull (^ _Nullable)())handler
- (void)convertObject:(id _Nonnull (^ __nullable)())handler
// the method accepts a nullable block that returns a nonnull value
// there are some more combinations here, you get the idea

作为结论,您可以使用其中任何一个,只要编译器可以确定要分配限定符的项目即可。

于 2015-11-12T21:59:20.160 回答
30

来自Swift 博客

此功能首次在 Xcode 6.3 中使用关键字 __nullable 和 __nonnull 发布。由于与第三方库的潜在冲突,我们在 Xcode 7 中将它们更改为您在此处看到的 _Nullable 和 _Nonnull。但是,为了与 Xcode 6.3 兼容,我们预定义了宏 __nullable 和 __nonnull 以扩展为新名称。

于 2015-11-23T04:14:37.723 回答
29

来自clang 文档

可空性(类型)限定符表示给定指针类型的值是否可以为空。

大多数情况下,您将使用nonnullnullable

以下是所有可用的说明符。从这篇文章

  • null_unspecified: 这是默认设置。它连接到一个 Swift 隐式解包的可选。
  • nonnull: 值不会为零。它连接到 Swift 常规引用。
  • nullable: 值可以为零。它连接到 Swift 可选。
  • null_resettable: 读取时该值永远不能为 nil,但您可以将其设置为 nil 以重置它。仅适用于属性。

无论您在属性或函数/变量的上下文中使用它们,上述符号都会有所不同:

指针与属性表示法

文章的作者还提供了一个很好的例子:

// property style
@property (nonatomic, strong, null_resettable) NSString *name;

// pointer style
+ (NSArray<NSView *> * _Nullable)interestingObjectsForKey:(NSString * _Nonnull)key;

// these two are equivalent!
@property (nonatomic, strong, nullable) NSString *identifier1;
@property (nonatomic, strong) NSString * _Nullable identifier2;
于 2016-01-07T20:20:15.273 回答
13

非常方便的是

NS_ASSUME_NONNULL_BEGIN 

并关闭

NS_ASSUME_NONNULL_END 

这将消除对代码级别'nullibis'的需求:-)因为除非另有说明,否则假设一切都是非空的(或nonnull_nonnull或)是有道理的。__nonnull

不幸的是,这也有例外......

  • typedefs 不被认为是__nonnull(注意,nonnull似乎不起作用,必须使用它的丑陋半兄弟)
  • id *需要一个明确的 nullibi 但哇罪税(_Nullable id * _Nonnull<-猜猜这意味着什么......)
  • NSError **始终假定为可为空

因此,除了异常的例外和引发相同功能的不一致关键字,也许方法是使用丑陋的版本__nonnull//并在编译__nullable__null_unspecified抱怨时交换......?也许这就是它们存在于 Apple 标头中的原因?

有趣的是,在我的代码中加入了一些东西......我讨厌代码中的下划线(老派 Apple C++ 风格的家伙),所以我绝对确定我没有输入这些,但它们出现了(几个例子中的一个):

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * __nullable credential );

更有趣的是,它插入 __nullable 的位置是错误的......(eek@!)

我真的希望我可以只使用非下划线版本,但显然编译器不会使用它,因为这被标记为错误:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
于 2015-11-18T14:03:19.457 回答