苹果在 Xcode 6.3 引入了一个 Objective-C 的新特性:Nullability Annotations,这一新特性的核心是两个新的类型修饰:__nullable和__nonnull。从字面上我们可知,__nullable表示对象可以是 NULL 或 nil,而__nonnull表示对象不应该为空。当我们不遵循这一规则时,编译器就会给出警告。在 Xcode 7 中,为了避免与第三方库潜在的冲突,苹果把__nonnull/__nullable改成_Nonnull/_Nullable。再加上苹果同样支持了没有下划线的写法nonnull/nullable,于是就造成现在有三种写法这样混乱的局面。但是这三种写法本质上都是互通的,只是放的位置不同,举例如下:
方法返回值修饰:
- (nullableNSString*)method;- (NSString* __nullable)method;- (NSString* _Nullable)method;
声明属性的修饰:
@property(nonatomic,copy,nullable)NSString*aString;
@property(nonatomic,copy)NSString* __nullableaString;
@property(nonatomic,copy)NSString* _Nullable aString;
方法参数修饰:
- (void)methodWithString:(nullableNSString*)aString;- (void)methodWithString:(NSString* _Nullable)aString;- (void)methodWithString:(NSString* __nullable)aString;
而对于双指针类型对象、Block 的返回值、Block 的参数等,这时候就不能用nonnull/nullable修饰,只能用带下划线的__nonnull/__nullable或者_Nonnull/_Nullable:
- (void)methodWithError:(NSError* _Nullable * _Nullable)error- (void)methodWithError:(NSError* __nullable* __null_unspecified)error;// 以及其他的组合方式
- (void)methodWithBlock:(nullablevoid(^)())block;// 注意上面的 nullable 用于修饰方法传入的参数 Block 可以为空,而不是修饰 Block 返回值;- (void)methodWithBlock:(void(^ _Nullable)())block;- (void)methodWithBlock:(void(^ __nullable)())block;
- (void)methodWithBlock:(nullableid__nonnull(^)(id__nullableparams))block;
// 注意上面的 nullable 用于修饰方法传入的参数 Block可以为空,而 __nonnull 用于修饰 Block 返回值 id 不能为空;
- (void)methodWithBlock:(id__nonnull(^ __nullable)(id__nullableparams))block;
- (void)methodWithBlock:(id_Nonnull (^ _Nullable)(id_Nullable params))block;
// the method accepts a nullable blockthat returns a nonnullvalue
// therearesomemore combinations here, yougetthe idea
以上基本上罗列了绝大部分的使用场景,但看完我们还是一脸懵逼啊,仍然不清楚什么时候应该用哪个修饰符!
在看了原生 iOS SDK 里 Foundation 和 UIKit 的头文件以及苹果的博文《Nullability and Objective-C》,我们总结如下使用规范:
对于属性、方法返回值、方法参数的修饰,使用:nonnull/nullable;
对于 C 函数的参数、Block 的参数、Block 返回值的修饰,使用:_Nonnull/_Nullable,建议弃用__nonnull/__nullable。
如果需要每个属性或每个方法都去指定nonnull和nullable,将是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable指针对象即可。如下代码所示:
NS_ASSUME_NONNULL_BEGIN
@interfacemyClass()
@property(nonatomic,copy)NSString*aString;
-(id)methodWithString:(nullableNSString*)str;
@end
NS_ASSUME_NONNULL_END
在上面的代码中,aString属性默认是nonnull的,methodWithString:方法的返回值也是nonnull,而方法的参数str被显式指定为nullable。
不过,为了安全起见,苹果还制定了以下几条规则:
通过typedef定义的类型的nullability特性通常依赖于上下文,即使是在 Audited Regions 中,也不能假定它为nonnull;
对于复杂的指针类型(如id *)必须显式去指定是nonnull还是nullable。例如,指定一个指向nullable对象的nonnull指针,可以使用__nullable id * __nonnull;
我们经常使用的NSError **通常是被假定为一个指向nullableNSError 对象的nullable指针。