剑客
关注科技互联网

_Nonnull 和 _Nullable 你注意到过没

这次咱们聊聊 Objc 中的 _Nonnull 和 _Nullable。尤其是经常使用 Objc 的开发者,大家是否在日常开发的时候注意到它们呢?

为什么出现

如果你在开发 Objc 项目的话,比如你有一段类似这样的代码:

@interface DataManager : NSObject

- (NSString *) loadDataByID: (NSString *) identity;

@end

可能就会收到这样的编译警告:

_Nonnull 和 _Nullable 你注意到过没

这个编译选项是告诉你要给引用类型的参数指定一个是否为空的标注。 这个特性是从 Xcode 6 加入的。 解决方法也不难, 只要加入相应的标注即可:

@interface DataManager : NSObject

- (NSString * _Nullable) loadDataByID: (NSString * _Nonnull) identity;

@end

在每个类型的后面, 加入了 _Nonnull 标记。 这样在编译的时候就不会出现警告了。 那么它们的作用是什么呢? 从名字上看, 大家应该也能猜出一二。 这个标记就是用来标注某个参数是否可以为空。 以我们上面这个例子来说, 就是它接受的 identiiy 参数不能为空, 但返回的 NSString 对象可以为空。 并且它还提供了编译器检查功能, 如果我们对上面那个方法传入 nil, 编译器是会检测出来,并给出警告的:

_Nonnull 和 _Nullable 你注意到过没

这个概念有点像 Swift 的 Optionals。 实际上 Objc 加入这个标注的初衷在很大程度上也是为了更好的兼容 Swift。 这点我们稍后会详谈。

当然, 上面 _Nonnull 和 _Nullable 这样的写法可能会让大家觉得和 Objc 整体的语法不太协调。 它还提供了另外一种写法:

@interface DataManager : NSObject

- (nullable NSString *) loadDataByID: (nonnull NSString *) identity;

@end

这里将 _Nonnull 和 _Nullable 替换成了 nonnull 和 nullable, 并且他们的位置挪到了类型声明的前面。 这种写法可读性会更好一点。

当然,这个标记除了用于参数上, 还能够用在属性定义中:

@interface DataManager : NSObject

@property (nonatomic, strong, nonnull) NSString *dbName;

@end

更简便的方法

除了上述我们说的几个显示定义这个标记之外。 Objc 还给我们一个宏定义:

NS_ASSUME_NONNULL_BEGIN

@interface DataManager : NSObject

- (nullable NSString *) loadDataByID: (NSString *) identity;

@end

NS_ASSUME_NONNULL_END

还是前面那个类。 这次用一了对内置宏 NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END。 这两个宏必须成对出现。这样,在他们两个之间所有的代码的默认标记都是 nonnull。 因此,就可以把修饰 identity 参数的那个标记省略掉了。

对 Swift 的改进

与其说 _Nonnull 和 _Nullable 这两个标记的加入是对 Objc 的改进, 我倒觉它更多的目的是为了让 Objc 对 Swift 的兼容性更好。

那这怎么说呢? 大家都知道 Swift 有 Optionals 类型, 但 Objc 中是没有这个概念的。 这就会导致那些 Objc 和 Swift 混编的项目中, Swift 从 Objc 桥接过来的代码变得很不自然。

比如我们前面定义的这个属性:

@property (nonatomic, strong) NSString *dbName;

这里面如果没有 nonnull 或 nullable 标记的帮助, Swift 就只能把它当做这样的类型:

var dbName: NSString!

末尾加叹号这种写法在 Swift 中是不推荐的,因为它违背了 Swift Optionals 的基本设计。 但如果我们用 nonnull 标记了它, Swift 就可以这样理解:

var dbName: NSString

把它当做一个永不为 nil 的属性, 或者我们用 nullable 标记, Swift 也可以正确的理解为 Optionals:

var dbName: NSString?

可见, 这两个标记除了优化 Objc 自身对于非空判断的不足。 还能够让它和 Swift 进行桥接的时候更加完善。

如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址