Objective-C中nullable,__nullable和_Nullable之间的区别

Leg*_*ess 144 nullable objective-c objective-c-nullability

使用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.

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

所以我的问题是:

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

Cri*_*tik 141

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
Run Code Online (Sandbox Code Playgroud)

对于参数:

- (void)doSomethingWithString:(nullable NSString *)str
- (void)doSomethingWithString:(NSString * _Nullable)str
- (void)doSomethingWithString:(NSString * __nullable)str
Run Code Online (Sandbox Code Playgroud)

对于属性:

@property(nullable) NSNumber *status
@property NSNumber *__nullable status
@property NSNumber * _Nullable status
Run Code Online (Sandbox Code Playgroud)

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

- (void)compute:(NSError *  _Nullable * _Nullable)error
- (void)compute:(NSError *  __nullable * _Null_unspecified)error;
// and all other combinations
Run Code Online (Sandbox Code Playgroud)

与接受块作为参数的方法类似,请注意nonnull/ nullablequalifier适用于块,而不是其返回类型,因此以下内容是等效的:

- (void)executeWithCompletion:(nullable void (^)())handler
- (void)executeWithCompletion:(void (^ _Nullable)())handler
- (void)executeWithCompletion:(void (^ __nullable)())handler
Run Code Online (Sandbox Code Playgroud)

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

- (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
Run Code Online (Sandbox Code Playgroud)

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

  • 似乎下划线版本可以在任何地方使用,所以我想我会一直使用它们,而不是在某些地方使用下划线版本而在其他地方没有下划线.正确? (2认同)

Ben*_*mas 26

来自Swift博客:

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

  • Xcode 7.0发行说明中还记录了这一点:"双重下划线的可空性限定符(__nullable,__ nonnull和__null_unspecified)已重命名为使用带有大写字母的单个下划线:分别为_Nullable,_Nonnull和_Null_unspecified."编译器预定义宏从旧的双未指定名称映射到源兼容性的新名称.(21530726)" (4认同)
  • 简而言之,单和双下划线版本是相同的. (3认同)

kga*_*dis 24

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

  • null_unspecified:桥接到Swift隐式解包的可选项.这是默认值.
  • nonnull:价值不会是零; 桥接到常规参考.
  • nullable:价值可以是零; 桥接到可选.
  • null_resettable:读取时,值永远不能为零,但您可以将其设置为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;
Run Code Online (Sandbox Code Playgroud)


Wil*_*iuk 12

非常方便

NS_ASSUME_NONNULL_BEGIN 
Run Code Online (Sandbox Code Playgroud)

和关闭

NS_ASSUME_NONNULL_END 
Run Code Online (Sandbox Code Playgroud)

这将使代码级别'nullibis'的需要无效:-)因为除非另有说明,否则假设所有内容都是非null(或nonnullor _nonnull__nonnull)是有意义的.

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

  • typedefs不被认为是__nonnull(注意,nonnull似乎不起作用,必须使用它的丑陋的同父异母兄弟)
  • id *需要一个明确的无效,但哇罪孽(_Nullable id * _Nonnull< - 猜这是什么意思......)
  • NSError ** 总是假定可以为空

因此,除了例外情况和不一致的关键字引出相同的功能之外,也许方法是使用丑陋的版本__nonnull/ __nullable/ __null_unspecified并在编译器抱怨时交换...?也许这就是为什么它们存在于Apple标题中?

有趣的是,有些东西把它放到我的代码中...我厌恶代码中的下划线(旧式的Apple C++风格的家伙)所以我绝对相信我没有输入这些但是它们出现了(几个例子):

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * __nullable credential );
Run Code Online (Sandbox Code Playgroud)

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

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

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
Run Code Online (Sandbox Code Playgroud)

  • 非下划线只能在左括号后直接使用,即(nonnull ...我敢肯定,只是为了使生活更有趣。 (2认同)