通常在其他语言中,nil 的本质就是 0,用 0 代表没有;但是 Swift 认为,0 应该是和其他整数一样的呀?程序员怎么知道 0 就是没有而不是 0 本身呢?于是,在 Swift 中出现了可选型的概念。在 Swift 中,nil 代表空,代表没有,nil 就是 nil,不再是一个整数 0 了。
可选型的基本使用
可选型写法很简单,普通类型后面加 ?
就可以了;通常一个变量如果不应该为空,一定要存在,那么就不应该设置为可选型,比如身份证号,每个人都一定有,那么就不要设置成可选型
1 | let id: Int |
如果这个变量可以为空,比如错误信息,错误信息可能有也可能没有,那么就适合设置成可选型
1 | var errorMessage: String? |
可选型的使用必须经过一个步骤就是解包(Unwrapp),可选型是无法直接使用的。
最简单的解包方式就是强制解包,含义为程序员保证这个可选型变量/常量一定不会空;而如果其真的为空了就会崩溃,因此这是有风险的,程序员必须能保证才行。
1 | var serverResponseCode: Int? = 404 |
通常情况下,比如网络请求得到的数据,我们无法保证其一定不会 nil,此时就应该通过判断来非暴力的解包,我们通常能想到的应该是下面这样
1 | if serverResponseCode != nil { |
事实上,因为可选型解包是一个常用操作,Swift 为我们专门提供了简化语法,下面的语法和上面的完全等价
1 | if let severResponseCode = serverResponseCode { |
如果有多个可选型同时需要解包,那么它们可以并列写在同一个 if 语句里
1 | var serverResponseCode: Int? = 404 |
当然,可以使用 if 的一般都可以使用 guard,本文就不涉及 guard 的使用了。
可见可选型的基本使用并不难,可为空就设置成可选型,用的时候就需要解包,这样就可以了。那么,Swfit 为什么要给程序员添加这个麻烦呢?当然是为了安全啦,可以这么说,只要程序员合理使用可选型,原来在 Objective-C 中的野指针访问的问题基本就可以避免啦。Swift 只是强制程序员去主动思考了而已,原来是被动的,现在是把这个过程提前到代码编写阶段,不得不去考虑。
可选型的实质
可选型事实上是一个枚举,String?
实际上是 Optional<String>
,它只是一个语法糖。下面两句代码是完全等价的
1 | var optionalString: String? = "Hello" |
而通常我们的解包
1 | if let aString = optionalString { |
其实也可以看作下面的过程
1 | switch optionalString { |
我们可以尝试自己写一个这样的枚举
1 | enum MyOptional<T> { |
Optional Chaining
我们知道可选型无法直接使用,那么我们就要先解包,然后使用,如果调用一个方法后返回类型还是可选型呢?那么我们再解包,再使用。那这样也太麻烦了吧?
所以呢,Swift 的这样一个语法就是解决这样的问题的,那就是 Optional Chaining,它的语法大约是下面这样的
1 | let aString: String? = "Hello world" |
一个可选型可以直接调用它对应的非可选型的方法,只不过返回值同样变成了一个可选型而已(即使这个方法的返回类型是一个非可选型),我们知道 description 的返回类型是 String 类型,可是此时 des 的类型是 String?
隐式可选型
在 UIKit 中,有很多类型是如 UIView!
这样的,比如 UIViewController 中的 view 属性,这就是隐式可选型。
隐式可选型是什么呢?其实也是一个可选型,也就是说 Int!
和 Int?
都是可选型,都可以为 nil;那么区别在哪呢?区别很小,就是 Int!
可以省去解包这个步骤直接使用
1 | let num: Int! = 3 |
所以很简单,Int!
就是把强制解包的步骤给提前做啦,其他跟一般的可选型没啥区别;当然,这样肯定是有风险的,因为是强制解包嘛。所以程序员声明隐式可选型闭包保证一点,就是在真正使用它的时候,其必须不为 nil。
那隐式可选型有什么作用呢?目前来讲,其应用场景基本就是在定义类的时候,类的某些属性我们可以定义成隐式可选型。下面这个例子来自慕课网刘宇波老师的案例
1 | class City { |
这个例子中, Country
类中的 capitalCity
属性被定义成了隐式可选型。
首先,为什么不能定义成一般的可选型呢?因为一个国家的首都都是确定存在的不可能为空啊,程序员其实不想把它定义成可选型,更不希望用户使用的时候还要解包。
那么就定义成非可选型呗?也不行,因为在构造函数里,我们在给 self.capitalCity
赋值的时候使用了 self
,我们知道一个类的对象在构造完成前(因为 self.capitalCity 属性没有被赋值嘛)是不可以被使用的,那这怎么办呢?既然要用这个对象就要赋值,既然要赋值就要用这个对象,死循环了。
所以解决办法就是在赋值之前就让这个对象先构造完成,也就是把这个属性改成可选型,这样可选型可以不用初始化(默认为 nil),之后呢我们马上再给这个对象的这个属性赋上值,最终在用户使用这个对象的时候,这个属性可以保证不为 nil,同时用户也不用解包即可直接使用了。