Objective-C 中可空、可空和可空的区别

在 Xcode 6.3中引入了新的注释,以便更好地表达 目标 C中 API 的意图(当然也是为了确保更好地支持 Swift)。这些注释当然是 nonnullnullablenull_unspecified

但是对于 Xcode 7,会出现很多警告,比如:

指针缺少一个可空性类型说明符(_ Nonnull、 _ Nullable 或 _ Null _ unspected)。

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

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

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

  • nonnullnullablenull_unspecified
  • _Nonnull_Nullable_Null_unspecified
  • __nonnull__nullable__null_unspecified

尽管我知道为什么以及在哪里使用哪个注释,但是我还是对应该使用哪种类型的注释、在哪里以及为什么使用感到有点困惑。我能得出的结论是:

  • 对于属性,我应该使用 nonnullnullablenull_unspecified
  • 对于方法参数,我应该使用 nonnullnullablenull_unspecified
  • 对于 C 方法,我应该使用 __nonnull__nullable__null_unspecified
  • 对于其他情况,如双指针,我应该使用 _Nonnull_Nullable_Null_unspecified

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

所以我的问题是:

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

47866 次浏览

来自 clang 文件:

Nullability (type)限定符表示给定指针类型的值是否可以为 null (_Nullable限定符) ,是否没有为 null (_Nonnull限定符)定义的含义,或者是否不清楚 null 的用途(_Null_unspecified限定符)。因为可空性限定符是在类型系统中表示的,所以它们比 nonnullreturns_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

总之,只要编译器能够确定要将限定符分配给哪个项,就可以使用其中任意一个。

非常方便

NS_ASSUME_NONNULL_BEGIN

最后

NS_ASSUME_NONNULL_END

这将消除对代码级别‘ nullibis’: ——的需求,因为除非另有说明,否则假设 一切是非 null (或 nonnull_nonnull__nonnull)是有意义的。

不幸的是,也有例外。

  • 不假设 typedef__nonnull(注意,nonnull似乎不工作,必须使用它的丑陋的同父异母兄弟)
  • id *需要一个明确的无效,但是令人惊讶的罪恶税(_Nullable id * _Nonnull <-猜猜这意味着什么...)
  • NSError **总是假定为可空的

因此,除了异常和不一致的关键字引出相同的功能,也许方法是使用丑陋的版本 __nonnull/__nullable/__null_unspecified和交换时,编译器抱怨... ?也许这就是为什么他们存在于苹果的标题?

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

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

更有趣的是,它插入 _ _ nullable 的地方是错误的... ... (eek@!)

我真的希望我可以只使用非下划线版本,但显然,这不与编译器飞行,因为这是标记为一个错误:

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

来自 斯威夫特博客:

这个特性最初是在 Xcode 6.3中使用关键字发布的 可以为空,也可以为非空。由于可能与第三方库发生冲突,我们在 Xcode 7中将它们更改为 _ Nullable 和 _ Nonnull 但是,为了与 Xcode 6.3兼容,我们已经 预定义的宏 _ _ nullable 和 _ _ nonnull 扩展到新名称。

来自 Clang 文档:

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

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

以下是所有可用的说明书。来自 翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳:

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

不管你是在属性的 背景还是在函数/变量的 背景中使用上面的符号,它们都是不同的:

Pointers vs. Properties notation

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

// 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;