修饰符: _ _nullable、_ _nonnull、_ _kindof - deepindo/DoNote GitHub Wiki

一、nullable、nonnull

在 Swift 中,我们会使用 ? 和 ! 去显式声明一个对象或者方法的参数是 optional 还是 non-optional ,而在 Objective-C 中则没有这一区分,这样就会带来一个问题:在 Swift 与Objective-C 混编时,Swift 编译器并不知道一个 Objective-C 对象或者一个方法的参数到底是 optional 还是 non-optional ,因此这种情况下编译器会隐式地都当成是 non-optional 来处理,这显然是不太好的。

为了解决上在这个问题,苹果在xcode 6.3引入了一个Objective-C的新特性:Nullability Annotations, 核心就是两个新的类型修饰符:_ nullable与 nonnull.其中 nullable表示对象可以是Null或者nil, 而 nonnull表示对象不对为空。 如果使用了对应的修饰符,但实际不遵循这一规则,编译器就会出现警告。为了避免与第三方库潜在的冲突,在xcode 7中apple将 nullable与 _nonnull(在xcode中显示两个分离的下划线,复制文本会显示成一个比较长的下划线)分别改成_Nullable与_Nonnull, 同时apple还支持没有双下划线的写法nullable与nonnull,于是就出现了三种写法混乱的局面。这三种写法本质上是互通,只是对应使用位置不同,场景不同。

对于__nullable__nonnull_Nullable_Nonnullnullablenonnull这三种写法,事实上可以在任何可以使用C语言关键字const的地方使用_Nullable和_Nonnull. 当然_Nullable和_Nonnull 一般用于指针类型,具体使用示例如下:

1.1 声明属性修饰符

@property (nullable, nonatomic, copy) NSString *string01;
@property (nonatomic, copy) NSString * _Nullable string02;
@property (nonatomic, copy) NSString * __nullable string03;

@property (nonnull, nonatomic, copy) NSString *string001;
@property (nonatomic, copy) NSString * _Nonnull string002;
@property (nonatomic, copy) NSString * __nonnull string003;

1.2. 方法返回值修饰符

- (nullable NSString *)function01;
- (NSString * _Nullable)function02;
- (NSString * __nullable)function03;

- (nonnull NSString *)function001;
- (NSString * _Nonnull)function002;
- (NSString * __nonnull)function003;

1.3. 方法参数修饰符

- (void)addTitle:(nullable NSString *)title image:(NSString * _Nullable)img content:(NSString *__nullable)content;
- (void)addNonTitle:(nonnull NSString *)title image:(NSString * _Nonnull)img content:(NSString *__nonnull)content;

1.4. 双指针类型对象

对于双指针的类型,就不能用nullable & nonnull来修饰了,只用用带下划线的_Nullable&_Nonnull 或者__nullable&__nonnull;

- (void)methodWithError:(NSError ** _Nullable)error error2:(NSError ** _Nonnull)error2;
- (void)methodWithLineError:(NSError ** __nullable)error error2:(NSError ** __nonnull )error2;

1.5. Block的返回值

- (void)methodWithBlock:(nullable void(^)(void))block;  /// 注意nullable修饰的是方法的参数block可以为空
- (void)methodWithBlock2:(void(^ _Nullable)(void))block;
- (void)methodWithBlock3:(void(^ __nullable)(void))block;

1.6. Block的参数

- (void)methodWithBlock4:(nullable id _Nonnull(^)(id _Nullable))block;
- (void)methodWithBlock5:(id _Nonnull(^ _Nullable)(id _Nullable))block;
- (void)methodWithBlock6:(id __nonnull(^ __nullable)(id __nullable))block;

注意上面方法methodWithBlock4中:nullable修饰的是方法的参数block可以为空, 而_Nonnull修饰的是block这个方法参数的返回值id不能为空;而方法methodWithBlock5与methodWithBlock6中nullable用的不同格式写在^后面,效果相同;

1.7. Nonnull Audited Regions

non-underscored方式比underscored(强调)的方式有很明显的优势,若每个属性或方法都需要去指定nullable & nonnull, 将是一件非常繁琐的事. Apple为了减轻工作量,专门提供了一对宏:NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END,这个称之为Nonnull Audited Regions,在这对宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定哪些指针对象为nullable即可,示例如下:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface DoTabBarC : UITabBarController

@property (nullable, nonatomic, copy) NSString *string01;
@property (nonatomic, copy) NSString * _Nullable string02;
@property (nonatomic, copy) NSString * __nullable string03;

- (nullable NSString *)function01;
- (NSString * _Nullable)function02;
- (NSString * __nullable)function03;

@end

NS_ASSUME_NONNULL_END

在上面的代码中,因为都处于NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END这对宏之间,所以string01、string02、string03默认都为nonnull,但因为了nullable不同格式的修饰,则最终为nullable. 剩下三个方法中的参数也是一样的性质。

1.8. 总结

以上基本罗列了大部分的使用场景,总结如下使用规范:

  • 对于在Nonnull Audited Regions中(可以不用nonnull相关的了,当然非要用也不会报错),属性、方法的参数、方法的返回值的修饰,使用nullable,对于C函数的参数、Block的参数、Block的返回值的修饰,建议使用_Nullable弃用__nullable
  • 对于非Nonnull Audited Regions中,对于属性、方法的参数、方法的返回值的修饰,使用nullable & nonnull; 对于C函数的参数、Block的参数、Block的返回值的修饰,建议使用_Nullable & _Nonnull弃用__nullable & __nonnull.

注意:

  • typedef类型不会被当做nonnull处理,即使在Nonnull Audited Regions内;
  • 很多复杂的指针,譬如id *必须明确标注,例如要制定一个非空的指针可以被空对象引用,要这样写_Nullable id * _Nonnull
  • 特定类型NSError **经常通过方法的参数返回错误以致它总是被假定为一个可以被空指针引用的空指针

二、__kindof

  • __kindof:一般用在方法返回值的前面修饰,表示返回值可以是当前类或者它子类,用在其他地方也同理。
  • __kindof书写格式:
放在类型前面,表示修饰这个类型(__kindof Person *),表示可以是Person类或者它的子类
  • 在定义初始化类方法返回值类型的时候,最早使用id作为返回值类型,后来使用instancetype,现在可以使用__kindof加类名

NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject

/*
  id表示返回值可以是任意类型,它的坏处: 
  1. 不能在编译的时候检查真实类型
  2. 返回值,没有提示
*/
//+ (id)person;

// instancetype:会自动识别当前对象的类,但是和__kindof相比它没有提示
//+ (instancetype)person;

// __kindof Person *:表示可以是Person类或者它的子类,和instancetype相比,在调用的时候,很清楚的知道返回类型
+ (__kindof Person *)person;

// 仅仅表示只能是Person类
+ (Person *)person1;

@end

NS_ASSUME_NONNULL_END

三、参考链接

https://developer.apple.com/swift/blog/?id=25