在Objective-C中,nullable、__nullable和_Nullable的区别。

时间:2022-12-28 21:28:38

With Xcode 6.3 there were new annotations introduced for better expressing the intention of API's in Objective-C (and to ensure better Swift support of course). Those annotations were of course nonnull, nullable and null_unspecified.

在Xcode 6.3中引入了新的注解,以更好地在Objective-C中表达API的意图(当然还要确保更好的快速支持)。当然,这些注释是非空的、可空的和不指定的。

But with Xcode 7, there is a lot of warnings appearing such as:

但是在Xcode 7中,会出现很多警告,比如:

Pointer is missing a nullability type specifier (_Nonnull, _Nullable or _Null_unspecified).

指针缺少一个可空类型说明符(_Nonnull, _Nullable或_null_unknown)。

In addition to that, Apple uses another type of nullability specifiers, marking their C code (source):

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

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

CFArrayRef __nonnull CFArrayCreate(
CFAllocatorRef __nullable分配器,const void * __nonnull * __nullable价值观,CFIndex numValues,const CFArrayCallBacks * __nullable回调);

So, to sum up, we now have these 3 different nullability annotations:

综上所述,我们现在有3种不同的可空性注解

  • nonnull, nullable, null_unspecified
  • null,可以为空,null_unspecified
  • _Nonnull, _Nullable, _Null_unspecified
  • _Nonnull、_Nullable _Null_unspecified
  • __nonnull, __nullable, __null_unspecified
  • __nonnull、__nullable __null_unspecified

Even though I know why and where to use which annotation, I'm getting slightly confused by which type of annotations should I use, where and why. This is what I could gather:

尽管我知道为什么以及在哪里使用哪个注释,但我还是有点搞不清应该使用哪种类型的注释、在哪里以及为什么。这就是我能收集到的:

  • For properties I should use nonnull, nullable, null_unspecified.
  • 对于属性,我应该使用nonnull, null_unknown。
  • For method parameters I should use nonnull, nullable, null_unspecified.
  • 对于方法参数,我应该使用非空的、可空的、空的。
  • For C methods I should use __nonnull, __nullable, __null_unspecified.
  • 对于C方法,我应该使用__nonnull, __nullable, __null_unknown。
  • For other cases, such as double pointers I should use _Nonnull, _Nullable, _Null_unspecified.
  • 对于其他情况,例如双指针,我应该使用_Nonnull, _Nullable, _null_。

But I'm still confused as to why we have so many annotations that basically do the same thing.

但我还是搞不懂为什么有这么多注解基本上都是一样的。

So my question is:

我的问题是:

What is exact difference between those annotations, how to correctly place them and why?

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

4 个解决方案

#1


116  

From the clang documentation:

叮当声的文档:

The nullability (type) qualifiers express whether a value of a given pointer type can be null (the _Nullable qualifier), doesn’t have a defined meaning for null (the _Nonnull qualifier), or for which the purpose of null is unclear (the _Null_unspecified qualifier). Because nullability qualifiers are expressed within the type system, they are more general than the nonnull and returns_nonnull attributes, allowing one to express (for example) a nullable pointer to an array of nonnull pointers. Nullability qualifiers are written to the right of the pointer to which they apply.

nullability (type)限定符表示给定指针类型的值是否可以为null (_Nullable限定符),null (_Nonnull限定符)是否没有定义意义,或者null的目的不明确(_null_unknown qualifier)。因为空属性限定符是在类型系统中表示的,所以它们比非空属性和returns_nonnull属性更通用,允许一个属性(例如)表示指向一个非空指针数组的可空指针。可空性限定符被写到它们所应用的指针的右边。

, and

,

In Objective-C, there is an alternate spelling for the nullability qualifiers that can be used in Objective-C methods and properties using context-sensitive, non-underscored keywords

在Objective-C中,可以用上下文敏感的非重点关键字在Objective-C方法和属性中使用nullability修饰符的替代拼写。

So for method returns and parameters you can use the the double-underscored versions __nonnull/__nullable/__null_unspecified instead of either the single-underscored ones, or instead of the non-underscored ones. The difference is that the single and double underscored ones need to be placed after the type definition, while the non-underscored ones need to be placed before the type definition.

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

Thus, the following declarations are equivalent and are correct:

因此,下列声明是等价的,是正确的:

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

For parameters:

的参数:

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

For properties:

属性:

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

Things however complicate when double pointers or blocks returning something different than void are involved, as the non-underscore ones are not allowed here:

当双指针或块返回与void不同的东西时,事情就变得复杂了,因为这里不允许非下划线:

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

Similar with methods that accept blocks as parameters, please note that the nonnull/nullable qualifier applies to the block, and not its return type, thus the following are equivalent:

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

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

If the block has a return value, then you're forced into one of the underscore versions:

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

- (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

As conclusion, you can use either ones, as long as the compiler can determine the item to assign the qualifier to.

作为结论,您可以使用任何一个,只要编译器可以确定要将限定符赋给的项。

#2


23  

From the Swift blog:

斯威夫特的博客:

This feature was first released in Xcode 6.3 with the keywords __nullable and __nonnull. Due to potential conflicts with third-party libraries, we’ve changed them in Xcode 7 to the _Nullable and _Nonnull you see here. However, for compatibility with Xcode 6.3 we’ve predefined macros __nullable and __nonnull to expand to the new names.

这个特性在Xcode 6.3中首次发布,关键字为__nullable和__nonnull。由于与第三方库的潜在冲突,我们在Xcode 7中将它们更改为_Nullable和_Nonnull。但是,为了与Xcode 6.3兼容,我们预定义了__nullable和__nonnull,以便扩展到新的名称。

#3


17  

I really liked this article, so I am merely showing what the author wrote: https://swiftunboxed.com/interop/objc-nullability-annotations/

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

  • null_unspecified: bridges to a Swift implicitly-unwrapped optional. This is the default.
  • null_unknown:连接到一个快速的隐含未包装的可选内容。这是默认的。
  • nonnull: the value won’t be nil; bridges to a regular reference.
  • 非空值:值不会是nil;桥梁,定期参考。
  • nullable: the value can be nil; bridges to an optional.
  • 可空:值可以为nil;一个可选的桥梁。
  • null_resettable: the value can never be nil when read, but you can set it to nil to reset it. Applies to properties only.
  • null_resettable:该值在读取时永远不会为nil,但是可以将其设置为nil来重置。只适用于属性。

The notations above, then differ whether you use them in the context of properties or functions/variables:

以上的表示法,然后在属性或函数/变量上下文中使用时有所不同:

在Objective-C中,nullable、__nullable和_Nullable的区别。

The author of the article also provided a nice example:

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

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

#4


9  

Very handy is

非常方便的是

NS_ASSUME_NONNULL_BEGIN 

and closing with

和关闭

NS_ASSUME_NONNULL_END 

This will nullify the need for the code level 'nullibis' :-) as it sort of makes sense to assume that everything is non-null (or nonnull or _nonnull or __nonnull) unless otherwise noted.

这将使代码级“nullibis”:-)的需要无效,因为假定所有内容都为非空(或非空或_nonnull或__nonnull)是有意义的,除非另有说明。

Unfortunately there are exceptions to this as well...

不幸的是,也有例外。

  • typedefs are not assumed to be __nonnull (note, nonnull does not seem to work, have to use it's ugly half brother)
  • typedef不被假定为__nonnull(注意,nonnull似乎不起作用,必须使用它是丑陋的半兄弟)
  • id * needs an explicit nullibi but wow the sin-tax ( _Nullable id * _Nonnull <- guess what that means...)
  • id *需要一个显式的nullibi,但是让sintax (_Nullable id * _Nonnull <-猜猜这是什么意思…)
  • NSError ** is always assumed nullable
  • NSError ** *总是假设为空

So with the exceptions to the exceptions and the inconsistent keywords eliciting the same functionality, perhaps the approach is to use the ugly versions __nonnull / __nullable / __null_unspecified and swap when the complier complains... ? Maybe that is why they exist in the Apple headers?

因此,除了例外和不一致的关键字引发相同的功能之外,可能的方法是在编译器报错时使用难看的__nonnull / __nullable / __null_unknown和swap……吗?也许这就是为什么它们存在于Apple header中?

Interestingly enough, something put it into my code... I abhor underscores in code (old school Apple C++ style guy) so I am absolutely sure I did not type these but they appeared (one example of several):

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

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

And even more interestingly, where it inserted the __nullable is wrong... (eek@!)

更有趣的是,插入__nullable的地方是错误的…(eek@ !)

I really wish I could just use the non-underscore version but apparently that does not fly with the compiler as this is flagged as an error:

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

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

#1


116  

From the clang documentation:

叮当声的文档:

The nullability (type) qualifiers express whether a value of a given pointer type can be null (the _Nullable qualifier), doesn’t have a defined meaning for null (the _Nonnull qualifier), or for which the purpose of null is unclear (the _Null_unspecified qualifier). Because nullability qualifiers are expressed within the type system, they are more general than the nonnull and returns_nonnull attributes, allowing one to express (for example) a nullable pointer to an array of nonnull pointers. Nullability qualifiers are written to the right of the pointer to which they apply.

nullability (type)限定符表示给定指针类型的值是否可以为null (_Nullable限定符),null (_Nonnull限定符)是否没有定义意义,或者null的目的不明确(_null_unknown qualifier)。因为空属性限定符是在类型系统中表示的,所以它们比非空属性和returns_nonnull属性更通用,允许一个属性(例如)表示指向一个非空指针数组的可空指针。可空性限定符被写到它们所应用的指针的右边。

, and

,

In Objective-C, there is an alternate spelling for the nullability qualifiers that can be used in Objective-C methods and properties using context-sensitive, non-underscored keywords

在Objective-C中,可以用上下文敏感的非重点关键字在Objective-C方法和属性中使用nullability修饰符的替代拼写。

So for method returns and parameters you can use the the double-underscored versions __nonnull/__nullable/__null_unspecified instead of either the single-underscored ones, or instead of the non-underscored ones. The difference is that the single and double underscored ones need to be placed after the type definition, while the non-underscored ones need to be placed before the type definition.

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

Thus, the following declarations are equivalent and are correct:

因此,下列声明是等价的,是正确的:

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

For parameters:

的参数:

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

For properties:

属性:

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

Things however complicate when double pointers or blocks returning something different than void are involved, as the non-underscore ones are not allowed here:

当双指针或块返回与void不同的东西时,事情就变得复杂了,因为这里不允许非下划线:

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

Similar with methods that accept blocks as parameters, please note that the nonnull/nullable qualifier applies to the block, and not its return type, thus the following are equivalent:

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

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

If the block has a return value, then you're forced into one of the underscore versions:

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

- (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

As conclusion, you can use either ones, as long as the compiler can determine the item to assign the qualifier to.

作为结论,您可以使用任何一个,只要编译器可以确定要将限定符赋给的项。

#2


23  

From the Swift blog:

斯威夫特的博客:

This feature was first released in Xcode 6.3 with the keywords __nullable and __nonnull. Due to potential conflicts with third-party libraries, we’ve changed them in Xcode 7 to the _Nullable and _Nonnull you see here. However, for compatibility with Xcode 6.3 we’ve predefined macros __nullable and __nonnull to expand to the new names.

这个特性在Xcode 6.3中首次发布,关键字为__nullable和__nonnull。由于与第三方库的潜在冲突,我们在Xcode 7中将它们更改为_Nullable和_Nonnull。但是,为了与Xcode 6.3兼容,我们预定义了__nullable和__nonnull,以便扩展到新的名称。

#3


17  

I really liked this article, so I am merely showing what the author wrote: https://swiftunboxed.com/interop/objc-nullability-annotations/

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

  • null_unspecified: bridges to a Swift implicitly-unwrapped optional. This is the default.
  • null_unknown:连接到一个快速的隐含未包装的可选内容。这是默认的。
  • nonnull: the value won’t be nil; bridges to a regular reference.
  • 非空值:值不会是nil;桥梁,定期参考。
  • nullable: the value can be nil; bridges to an optional.
  • 可空:值可以为nil;一个可选的桥梁。
  • null_resettable: the value can never be nil when read, but you can set it to nil to reset it. Applies to properties only.
  • null_resettable:该值在读取时永远不会为nil,但是可以将其设置为nil来重置。只适用于属性。

The notations above, then differ whether you use them in the context of properties or functions/variables:

以上的表示法,然后在属性或函数/变量上下文中使用时有所不同:

在Objective-C中,nullable、__nullable和_Nullable的区别。

The author of the article also provided a nice example:

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

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

#4


9  

Very handy is

非常方便的是

NS_ASSUME_NONNULL_BEGIN 

and closing with

和关闭

NS_ASSUME_NONNULL_END 

This will nullify the need for the code level 'nullibis' :-) as it sort of makes sense to assume that everything is non-null (or nonnull or _nonnull or __nonnull) unless otherwise noted.

这将使代码级“nullibis”:-)的需要无效,因为假定所有内容都为非空(或非空或_nonnull或__nonnull)是有意义的,除非另有说明。

Unfortunately there are exceptions to this as well...

不幸的是,也有例外。

  • typedefs are not assumed to be __nonnull (note, nonnull does not seem to work, have to use it's ugly half brother)
  • typedef不被假定为__nonnull(注意,nonnull似乎不起作用,必须使用它是丑陋的半兄弟)
  • id * needs an explicit nullibi but wow the sin-tax ( _Nullable id * _Nonnull <- guess what that means...)
  • id *需要一个显式的nullibi,但是让sintax (_Nullable id * _Nonnull <-猜猜这是什么意思…)
  • NSError ** is always assumed nullable
  • NSError ** *总是假设为空

So with the exceptions to the exceptions and the inconsistent keywords eliciting the same functionality, perhaps the approach is to use the ugly versions __nonnull / __nullable / __null_unspecified and swap when the complier complains... ? Maybe that is why they exist in the Apple headers?

因此,除了例外和不一致的关键字引发相同的功能之外,可能的方法是在编译器报错时使用难看的__nonnull / __nullable / __null_unknown和swap……吗?也许这就是为什么它们存在于Apple header中?

Interestingly enough, something put it into my code... I abhor underscores in code (old school Apple C++ style guy) so I am absolutely sure I did not type these but they appeared (one example of several):

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

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

And even more interestingly, where it inserted the __nullable is wrong... (eek@!)

更有趣的是,插入__nullable的地方是错误的…(eek@ !)

I really wish I could just use the non-underscore version but apparently that does not fly with the compiler as this is flagged as an error:

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

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