首页 Swift 5.1 学习 (5) : 可选类型
文章
取消

Swift 5.1 学习 (5) : 可选类型

可选类型

Optional 我们翻译做 可选类型, 在一个 常量 或者 变量 的值可能等于 nil 的情况下使用. 一个 Optional 的的变量, 他可能有一个 真实存在的值 , 也可能为 nil.

提示 Optional 的概念在 C语言OC 中是不存在的. OC 中最接近的是能够从一个方法返回 nil 或者 一个对象. nil 所表达的意思就是 缺少一个有效的对象. 然而, 这仅仅适用于对象, 对于 结构体枚举类型 并不适用. 对于这些类型, OC 方法通常返回一个特殊的值 (比如: NSNotFound) 来表示 无值. 这种方式通常是建立在方法的调用者已经知道要测试一个特殊值的条件之下. SwiftOptional 允许任何类型都没有值, 而不需要特殊的常量.

那么如何使用 Optional 来处理空值的情况呢?

举个例子: 比如我们需要把 String 类型转换为 Int 类型. 但是并不是所有的 String 类型的对象都可以转换为 Int 类型. 比如 "123" 可以转换成 123, 但是 "Hello World" 就不能转换为 Int 类型.

下面的事例就是使用 Int 的初始化方法尝试把 String 转换为 Int 类型:

1
2
3
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 的推断类型就是 "Int?", 或者 "optional Int"

因为 Int 的初始化方法可能执行失败, 因此他返回的是 Optional 类型的 Int? 而不是 Int 类型. 这个事例中 convertedNumber 的值可能是一个 Int 类型的值, 也可能初始化失败而 无值.

nil

你可以通过给一个 Optional 的变量赋值为 nil 来表示其 没有值.

1
2
3
4
var serverResponseCode: Int? = 404
// serverResponseCode 有一个实际存在的 Int 类型的值 404
serverResponseCode = nil
// serverResponseCode 当前没有值

提示 你不能把 nil 赋值给一个 非 Optional常量/变量. 在开发中, 如果一个值在某些情况需要处理 无值 的情况, 你就需要声明其为适当的 Optional 类型.

如果你声明了一个 Optional变量 而未设置其 初始值, 这个 变量 的值就会被默认的设置为 nil.

1
2
var surveyAnswer: String?
// surveyAnswer 被默认设置为 nil

注意SwiftOC 中, nil 意义是不同的. 在 OC 中, nil 表示指向不存在的对象. 在 Swift 中, nil 并不是一个 指针, 他表示的是某个类型值的缺失. 所有 Optional 都可以被设置为 nil, 不仅仅是 对象类型.

if 语句 和 强制解包

你可以使用 if 语句对 Optionalnil 进行对比来判断其是否 有值. 你可以使用 == 或者 != 来对其进行比较:

1
2
3
4
if convertedNumber != nil {
    print("convertedNumber 有值.")
}
// 打印结果: "cconvertedNumber 有值."

如果你能够肯定一个 Optional 有值, 你可以通过在 变量名 的尾部加上 ! 来访问其存储的值. 这被称为 强制解包.

1
2
3
4
if convertedNumber != nil {
    print("convertedNumber 的值为 \(convertedNumber!).")
}
// 打印结果: "convertedNumber 的值为 123."

注意 如果尝试使用 ! 去访问一个 无值Optional变量 的值时将会触发运行时错误. 在使用 ! 进行 强制解包 之前, 必须确保其存在一个 非 nil 的值.

可选绑定

你可以使用 可选绑定 来确定一个 Optional 是有 有值. 如果有值, 则把值作为一个临时 常量/变量 来使用. 可选绑定 可以与 ifwhile 一起使用, 来检查 Optional 中的值.

使用 if 来写一个 可选绑定 的事例:

1
2
3
4
5
6
if let actualNumber = Int(possibleNumber) {
    print("\"\(possibleNumber)\" 可以转换为 \(actualNumber)")
} else {
    print("字符串 \"\(possibleNumber)\" 不能转换为 Int 类型")
}
// 打印结果: "123" 可以转换为 123

这段代码可以理解为:

如果 possibleNumber 可以通过 Int(possibleNumber) 返回一个 Int 类型的值(非 nil 的值). 就把这个值复制给一个新的常量 actualNumber (此处的 actualNumberInt 类型, 而非 Int?).

如果转换成功, actualNumber 就可以在 if 语句的第一个分之内直接使用. actualNumber 初始化的时候已经被赋予了 possibleNumber 的值, 因此无需使用 ! 来强制解包获取他的值.

你可以使用 常量 或者 变量 来进行 可选绑定. 如果你需要在 if 语句的第一个分支中操作 actualNumber, 则可以使用 if var actualNumber 来代替事例代码中的相应部分.

你可以在一个 if 判断语句中包含多个 可选绑定布尔条件, 但是你需要使用 "," 来对其进行分隔. 如果 if 的判断语句中有任意一个 Optional 的值为 nil 或者任意一个 布尔条件 的结果为 false, 则整个 if 判断条件的结果都将为 false.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 打印结果 "4 < 42 < 100"

if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// 打印结果: "4 < 42 < 100"

提示if 语句中使用 可选绑定 创建的 常量变量 只能在 if 语句的主体中使用. 使用 guard 语句创建的 常量变量 可以在 guard 后面的代码中使用.

隐式解包

在上文的描述中, 我们知道了 Optional常量 或者 变量 允许 无值 的状态. 可以通过 if 语句来检查 Optional 是否 有值. 并且可以通过 可选绑定 来获取内部的值.

在有些时候,从程序的结构可以清楚地看出 Optional 一定有值, 比如在第一次赋值之后. 在这种情况下, 我们可以安全的假设 Optional 是一定有值的, 所以在存取值的时候就可以省略掉 检查解包 的操作.

这种 Optional 被定义为 implicitly unwrapped optionals (隐式解包的可选类型). 其定义方法为在类型的尾部使用 ! (String!) 而非 ? (String?).

隐式解包的 Optional 类型其背后是一个普通的 Optional, 但也可以像 非Optional 类型一样使用, 而不需要每次访问的时候都进行解包.

以下案例对 String?String! 进行了对比:

1
2
3
4
5
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要 !

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 无需 !

你可以理解为可隐式展开的 Optional 在使用的时候可以自动解包. 每次在使用 Optional 的时候不是在 变量名 后面添加 !, 而是声明的时候在 类型 的后面添加 !.

注意 如果一个可隐式解包的 Optional 的值为 nil, 当你尝试读取其值的时候, 就会触发运行时错误. 这和你在一个 无值 的普通 Optional 变量名结尾添加 ! 是一样的.

你也可以像普通 Optional 一样来检查一个可隐式解包的 Optional 是否有值:

1
2
3
4
if assumedString != nil {
    print(assumedString!)
}
// 打印结果: "An implicitly unwrapped optional string."

你也可以使用 可选绑定 来检查和获取 Optional 的值:

1
2
3
4
if let definiteString = assumedString {
    print(definiteString)
}
// 打印结果: "An implicitly unwrapped optional string."

提示 当变量的值可能在稍后变为 nil 的情况下, 不要使用隐式解包的 Optional 类型. 在变量的生命周期内如果需要检查其值是否为 `nil’, 建议使用普通的 Optional 类型.

本文由作者按照 CC BY 4.0 进行授权