0%

OC nullability特性

OC nullability特性

众所周知,Swift语言是严格区分可选和非可选变量的.比如UIViewUIView?.而在之前的OC中却没有这个概念,二者都使用UIView*表示.由于Swift编译器无法根据UIView*判断该变量是否是可选的.因此该类型在Swift中被解释为隐式解包UIView!.而在Swift中对一个值为nil的可选变量进行强制解包时将导致崩溃.因此你必须时刻警惕UIView!变量在某处会不会被赋值为nil.这不仅带来使用上的不便,而且也影响编程体验.因此有必要让OC兼容Swift的Optional特性,方便OC的类在Swift中的使用.于是在Xcode 6.3 开始官方便给OC添加了nullability功能.

nullability 特性包含_Nullable_Nonnull这两种修饰符._Nullable表明该变量可以为nil或NULL值.而_Nonnull表明该变量不能为nil值.两个变种写法nullable,nonnull.

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#import <Foundation/Foundation.h>
#import "Person.h"

#define myCase 4

#if myCase == 1

//不使用
@interface Book : NSObject

- (instancetype)initWithName:(NSString *)name;

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *version;
@property (nonatomic, assign) long words;
@property (nonatomic, copy) NSString *publicID;
@property (nonatomic, copy) NSString *url;
@property (nonatomic, strong) Person *author;

@end

/**
Swift接口如下:
open class Book : NSObject {


public init!(name: String!)


open var name: String!

open var version: String!

open var words: Int

open var publicID: String!

open var url: String!

open var author: Person!
}
*/

#elif myCase == 2

//推荐写法.属性都不可为nil
NS_ASSUME_NONNULL_BEGIN

@interface Book : NSObject

- (instancetype)initWithName:(NSString *)name;

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *version;
@property (nonatomic, assign) long words;
@property (nonatomic, copy) NSString *publicID;
@property (nonatomic, copy) NSString *url;
@property (nonatomic, strong) Person *author;

@end

NS_ASSUME_NONNULL_END

#elif myCase == 3

//推荐写法,个别属性可为nil
NS_ASSUME_NONNULL_BEGIN

@interface Book : NSObject

- (instancetype)initWithName:(NSString *)name;

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *version;
@property (nonatomic, assign) long words;
@property (nonatomic, copy, nullable) NSString *publicID;
@property (nonatomic, copy, nullable) NSString *url;
@property (nonatomic, strong) Person *author;

@end

NS_ASSUME_NONNULL_END

#elif myCase == 4

//全部自己手动定义
@interface Book : NSObject

- (nonnull instancetype)initWithName:(NSString * _Nonnull)name;

@property (nonatomic, copy) NSString * _Nonnull name;
@property (nonatomic, copy, nonnull) NSString *version;
@property (nonatomic, assign) long words;
@property (nonatomic, strong, nonnull) NSNumber *barCode;
@property (nonatomic, copy, nullable) NSString *publicID;
@property (nonatomic, copy, nullable) NSString *url;
@property (nonatomic, strong, nonnull) Person *author;
@property (nonatomic, strong, nonnull) NSMutableArray<Person *> *seeker;

@end

#endif

nullability特性对OC编码的影响

在OC中即使标明为_Nonnull,你依然可以给它一个nil值,但编译器会给出警告,仅仅是警告.因此在OC中不要对_Nonnull修饰的变量抱有一定不为nil的幻想,否则该崩还得崩.

nullability特性只是一种约定,在开发的过程中我们要遵守这一约定.如果一个变量是nonnull的,我们在init方法中一定要给它一个初始值,并且在使用过程中不要给它赋值为nil.

nullability特性对OC,Swift混编的影响

nullability特性其实主要还是为了方便OC的类在Swift中的使用.

一个_Nonnull修饰的OC变量,它在OC代码中可能会被赋值为nil,当它在Swift中使用时nil会变成<uninitialized>对象.奇怪的是如果是nil的NSString则会被自动转换为空字符"".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;

- (void)hello;

@end

NS_ASSUME_NONNULL_END


@interface Book : NSObject

- (nonnull instancetype)initWithName:(NSString * _Nonnull)name;

@property (nonatomic, copy) NSString * _Nonnull name;
@property (nonatomic, copy, nonnull) NSString *version;
@property (nonatomic, assign) long words;
@property (nonatomic, strong, nonnull) NSNumber *barCode;
@property (nonatomic, copy, nullable) NSString *publicID;
@property (nonatomic, copy, nullable) NSString *url;
@property (nonatomic, strong, nonnull) Person *author;
@property (nonatomic, strong, nonnull) NSMutableArray<Person *> *seeker;

@end

@objc func printBook() {
let name = book.name

let version = book.version
print(version)

let seeker: NSMutableArray = book.seeker
seeker.add("sd") //没效果
print(seeker) //崩溃:Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)

let num: NSNumber = book.barCode
// print(num) //崩溃
let i = num.intValue //0

let author = book.author
let authorName = author.name

let publicID = book.publicID ?? "unknown"
let url = book.url ?? "unknown"
let string = name + "-" + version + "-" + authorName + "-" + publicID + "-" + url

author.hello() //不会执行
let des = String.init(describing: author) //空字符串
print("des:\(des)")
// print(author) //崩溃

// let s: AnyClass = type(of: author) //崩溃

var arr: [Person] = []
arr.append(author)

print("\(author)", "arr个数:\(arr.count)", arr)

print("\(string)")
}

打印

1
2
 arr个数:1 []
三国---unknown-unknown

其中的authorName将会是空字符串"";而author将会是<uninitialized>,其实就是一个nil对象,给它发送消息没有什么问题,但调用Swift中的某些方法比如print(author)type(of: author)将导致野指针崩溃:Thread 1: EXC_BAD_ACCESS (code=1, address=0x0).

OC和Swift语言其他的一些设计差别

Swift中,如果一个变量没有被初始化,它是不能被使用的,编译器会报错.

1
2
3
4
{
var peron: Person
peron.hello() //Variable 'peron' used before being initialized
}

但OC中,声明一个变量后没有给它一个值也能够给它发送消息.

1
2
3
4
5
6
{
Person *p;
[p hello];
Class cls = p.class;
NSLog(@"cls:%@", cls); // cls:(null)
}

可以看出Swift对初始化是非常严格的.而OC基本没啥限制.

总结

nullability特性只是一种约定,在开发的过程中我们要遵守这一约定.如果一个变量是nonnull的,我们在初始化方法中一定要给它一个初始值,并且在使用过程中不要给它赋值为nil.

OC,Swift混编时,为了使用方便并减少可能的解包崩溃,推荐使用nullability特性,旧的OC代码也有必要添加.

在Swift中尽量不使用强制解包.

觉得文章有帮助可以打赏一下哦!