Swift 可选类型、隐式可选类型与可选链
在 OC 中 nil 表示一个指向不存在的对象的指针,而 Swift 中 nil 不是指针,而是一个确定的值,它表示一切值缺失的情况,所谓值缺失就是一个和 Swift 的可选类型相关的概念。
可选类型 optional
可选类型的变量可以有值,也可以无值,无值的变量取值得到的就是 nil,相对应的,非可选类型的变量必须有值,也不能赋值为 nil,如果没有值就会报编译期错误。Swift 声明一个变量时默认情况下就是非可选的,即必须给这个变量赋值一个非空值。之所以引入可选类型和非可选类型是为了增强便一阶段的安全检查,对于因 nil 引发的崩溃可以在编译期就检查出来,避免崩溃发现时间延时到运行时。
声明一个可选类型变量和不可选类型变量如下
var optionalVariable:String? = nil
var nonOptionalVariable:String = "Yasic"
当然可选类型变量默认不赋值的时候就是 nil,并且对于一个类中的非可选类型变量,也可以将赋值操作延时到初始化 init 方法 中。
那么对于非可选类型参数就可以安全取值安全使用了,而可选类型参数则需要在使用前检查变量是否有值,通常我们会这样检查
if optionalVariable != nil {
optionalVariable!.append("test")
}
其中用到了感叹号运算符,它表示对可选值的强制解析,表明开发者明确知道这个可选类型变量在此处是有值的所以强制从中取值,但是如果变量中的确没有值就会引发运行时崩溃了。
所以强制解析功能应当谨慎使用,Swift 更推荐的方式是可选绑定(Optional Binding),与强制解析不同,可选绑定会判断可选类型是否有值,如果有值就将其赋值给一个临时常量或变量,可选绑定可以用在 if 和 while 语句中来对可选类型的值进行判断后赋值给临时常量或变量。
if let tempVariable = self.optionalVariable {
print(tempVariable)
}
临时变量也可以是 var 型的,从而可以在分支中改变它的值。在分支中使用到临时变量时能保证它一定值,从而就避免了强制解析的过程。
隐式可选类型
可选类型变量增强了代码的安全性,但是每次使用到可选类型变量都需要加判断或者强制解析比较麻烦,这时可以利用隐式可选类型。隐式可选类型实际就是一个普通的可选类型,但是开发者明确知道此可选类型变量在第一次赋值后一定总有值,从而用隐式可选类型省却了强制解析的过程。
声明一个隐式可选类型的方式
var hiddenOptinalVariable:String! = nil
使用时可以直接取值,此时表明开发者能明确保证可以取到值,但是如果的确没有值,就会引发运行时崩溃。
hiddenOptinalVariable.append("yasic")
当然也可以用上面的方法对隐式可选类型进行判空
if var temp = self.hiddenOptinalVariable {
temp.append("yasic")
}
可选类型的应用
空合运算符 Nil Coalescing Operator
空合运算符 "??" 是对三目运算符的简化,针对可选类型变量 a 进行空判断,若非空则返回 a 的变量值,否则返回空合运算符后的默认值 b。它的使用有两个条件
- a 必须是可选类型
- b 的类型必须与 a 一致
var temp = self.hiddenOptinalVariable ?? "123"
方法返回可选类型值
可选类型变量可以出现在方法的返回值当中,表明此方法的返回值能为 nil
func returnOptinalVariable(input:Bool) -> String? {
if !input {
return nil
} else {
return "true"
}
}
类返回可选类型对象
对于 Swift 的类有一个特殊的方法,构造方法 init,这个方法也可以返回一个可选类型的值,它的主要意义在于能够根据初始化时传入的值以及环境参数来判断是否可以返回一个实例对象,通常也称这类构造方法为可失败构造器。
convenience init?(input:Bool){
if !input {
return nil
}
self.init()
}
闭包返回可选类型值
闭包返回可选类型闭包,表明此闭包可能为 nil
func returnOptionalBlock(value: Bool) -> (() -> (Void))? {
if value {
return { () in
print("可选闭包")
}
} else {
return nil
}
}
if let voidBlock = self.returnOptionalBlock(value: false) {
voidBlock()
}
要注意对于为 nil 的闭包进行强制解析执行会发生运行时错误 "Fatal error: Unexpectedly found nil while unwrapping an Optional value"。
结构体和枚举的可失败构造器
类似类的可失败构造器,结构体的可失败构造器也是支持的
enum OperationError:Error {
case ErrorFirst
case ErrorSecond
case ErrorThird
init?(type:String) {
switch type {
case "first":
self = .ErrorFirst
case "second":
self = .ErrorSecond
case "third":
self = .ErrorThird
default:
return nil
}
}
}
struct PersonStruct {
let name: String
init?(name: String) { // 可失败构造器
if name.isEmpty { return nil } // 如果实例化为空串,则返回nil(即实例化失败)
self.name = name
}
}
错误处理中的可选类型
对于可能抛出错误的方法调用时应当使用 try 语法来捕捉错误,当然有时候并不对捕捉到的错误进行处理,此时代码可能会写成这个样子
let picture = try! loadImage(atPath: "...")
这样其实是禁止了错误的抛出,一旦有错误时就会崩溃,所以建议的语法如下
let picture = try? loadImage(atPath: "...")
类型转换中的可选类型
对于某种类型的变量进行向上转换时不会出现失败的情况,但是向下转型就可能会失败
let yasic = Person(name: "Yasic")
let yasicCopy = yasic as Male
这样做会有编译期报错 'Person' is not convertible to 'Male'; did you mean to use 'as!' to force downcast?
此时建议使用可选绑定来进行类型向下转换
let yasic = Person(name: "Yasic")
if let yasicCopy = yasic as? Male {
}
可选链 Optional Chaining
Swift 对可选链的定义如下
可选链是一种可以请求和调用属性、方法及下标脚本的过程,它的可选性体现在请求或调用的目标当前可能为空 nil。如果可选的目标有值,那么调用就会成功;相反,如果可选的目标为 nil,那么调用就会返回 nil。多次请求或调用可以被链接在一起形成一个链,如果任何一个节点为空 nil 将导致整个链失效。
此处假设有两个类 Person 和 Room
class Person {
var name:String?
var room:Room?
init(name:String) {
self.name = name
}
func nameStr() -> String? {
if let name = self.name {
return name
}
return nil
}
}
class Room {
var roomAddr:String?
var roomArea:Int
var people:[String]?
init(roomAddr:String, roomArea:Int) {
self.roomAddr = roomAddr
self.roomArea = roomArea
}
func roomAddrStr() -> String? {
if let addr = self.roomAddr {
return addr
}
return nil
}
}
可以看到,Person 有一个可选类型的成员变量 room,表示它可能为 nil,也可能有值,还有一个返回可选类型值的方法 nameStr,Room 类也有一个返回可选类型值的方法 roomAddrStr。
下面是一些具体的使用
- 可选链调用属性
if let area = yasic.room?.roomArea {
}
这样获取的就是一个 Int? 类型的值,而不是 roomArea 所定义的 Int 类型。
- 可选链调用方法
if let result = yasic.room?.roomAddrStr() {
}
- 可选链访问数组下标
if let result = yasic.room?.people?[1] {
}
需要注意的是可选链始终返回的是可选类型变量,不会因为最后一层的变量是非可选类型变量就返回非可选类型变量,并且可选链中任意一节失败就会直接返回 nil。