Objective-C中可为空,__ nullable和_Nullable之间的区别


154

借助Xcode 6.3,引入了新的注释,以便更好地表达Objective-C中API的意图(当然可以确保更好的Swift支持)。这些注解当然nonnullnullablenull_unspecified

但是使用Xcode 7时,会出现很多警告,例如:

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

除此之外,Apple使用另一种可为空性说明符,标记其C代码(source):

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

但是对于为什么我们有这么多基本上执行相同操作的注释,我仍然感到困惑。

所以我的问题是:

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


3
我已经阅读了该文章,但是并没有解释其中的区别以及为什么我们现在拥有3种不同类型的注释,我真的很想了解为什么他们继续添加第三种类型。
Legoless

2
这确实对@ Cy-4AH没有帮助,您知道这一点。:)
Legoless 2015年

@Legoless,确定要仔细阅读吗?它准确地解释了您应在何处以及如何使用它们,经过审核的范围,何时可以出于更好的可读性,兼容性原因等原因使用另一个范围等……您可能不知道自己真正想问什么,但是答案显然在链接下。也许只是我一个人,但我认为不需要任何进一步的解释来解释其目的,我想,将简单的解释复制并粘贴到此处作为答案是非常尴尬的。:(
Holex 2015年

2
它确实清除了某些部分,但是不,我仍然不明白为什么我们不仅仅具有第一个注释。它仅说明了为什么它们从__nullable变为_Nullable,但没有说明为什么如果我们有nullable甚至需要_Nullable。并且它也没有解释为什么苹果仍然在自己的代码中使用__nullable。
Legoless

Answers:


152

clang 文档中

可空性(类型)限定符表示给定指针类型的值是否可以为null(_Nullable限定符),没有为null定义的含义(_Nonnull限定符),或者其用途不明确(_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

结论是,只要编译器可以确定要分配限定符的项目,就可以使用任何一种。


2
下划线版本似乎可以在任何地方使用,因此我想我会一直使用它们,而不是在某些地方使用带下划线的版本,而在其他地方则不使用带下划线的版本。正确?
Vaddadi Kartick '16

@KartickVaddadi是的,正确的,您可以一致地使用单下划线或双下划线版本。
Cristik '16

_Null_unspecified在斯威夫特这转化为可选的?非可选还是什么?
亲爱的

1
@Honey _Null_unspecified已作为隐式展开的可选内容导入到Swift中
-Cristik,

1
@Cristik啊哈 我猜这是它的默认值...因为当我没有 指定时,我得到了隐式解包的可选值...
Honey

28

来自Swift博客

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


4
简而言之,单下划线和双下划线的版本是相同的。
Vaddadi Kartick '16

4
还记录在Xcode 7.0发行说明中:“双下划线的可空性限定符(__nullable,__ nonnull和__null_unspecified)已重命名为使用单个大写字母的下划线,分别为_Nullable,_Nonnull和_Null_unspecified。)编译器预定义宏从旧的双未指定名称映射到新名称以实现源兼容性。(21530726)“
Cosyn 2016年

25

我真的很喜欢这篇文章,所以我只是展示作者写的内容:https : //swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:桥接到Swift隐式展开的可选。这是默认值
  • nonnull:值不会为零;过渡到常规参考。
  • nullable:值可以为nil;桥接到可选。
  • 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;

12

非常方便的是

NS_ASSUME_NONNULL_BEGIN 

然后关闭

NS_ASSUME_NONNULL_END 

除非另有说明,否则这将使对代码级'nullibis':-)的需求无效,因为假定所有内容均为非null(或nonnullor _nonnull或or __nonnull)是有意义的。

不幸的是,这也有例外。

  • typedef不假定s是__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 );

2
非下划线只能
Elise van Looij

如何使id <> nonnull?我觉得这个答案包含很多知识,但缺乏明确性。
fizzybear19年

1
诚然,@ fizzybear我通常采取相反的方法。指针是我的朋友,自90年代初以来,我还没有遇到过“ null” /“ nil”指针问题。我希望我可以使整个可为空/不可为零的东西消失。但是,真正的答案是答案的前6行。但是关于您的问题:我不会做(不会批评),所以我只是不知道。
威廉·塞尼乌克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.