百日学 Swift(Day 12) – optionals, unwrapping, and typecasting(可选项、解包和类型转换)
1. Handling missing data(处理丢失的数据) – test
其实应该翻译成处理未知数值比较贴切。当不能确定存储属性的值时,Swift 提供了可选项
的方式,表示该属性可能为nil
(空)。要使类型可选,在其后加上?
即可。
var age: Int? = nil
2. Unwrapping optionals(可选项的解包) – test
var number: Int?
由于可选项可能为 nil,所以在使用的时候要先确保其有值,因为 nil 在绝大多数时候是不能参与运算的。
number = 2
print(number? * 10)
这样会报错:Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
使用!
可以对可选项强行解包,但是前提是可选项不能是 nil。如果 number 是 nil 还是会报错的。
print(number! * 10)
可以用??
加上默认值的方法避免空值。但这种方法有局限性。
print((a ?? 0) + 2) // 如果 a 是 nil,就取默认值 0
使用if let
方式可以较好地处理。
if let unwrapped = number {
print("\(unwrapped * 10)") // number 不为 nil 时
} else
print("Missing number.") // unwrapped 为 nil,即 number 为 nil 时
}
3. Unwrapping with guard(使用 guard 解包) – test
if let
的替代方法是guard let
,具体用法是在guard let
后面直接写else
和处理无值情况的闭包,如果有值则继续执行后面的语句。
func greet(_ name: String?) {
guard let unwrapped = name else { // 处理空值,注意 else
print("You didn't provide a name!")
return
}
print("Hello, \(unwrapped)!") // 注意,这里不再使用 name,而是要用保证有值的 unwrapped
}
4. Force unwrapping(强制解包) – test
使用!
可以对可选项强行解包,但是可能会因为出问题。比如,如果尝试将字符串转换为整数,会因为转换失败造成程序崩溃。!
通常被称为“崩溃操作符”。程序员必须对程序中每个!
负责,只有确定是安全的时候才应强制解包。
let danjia = "42" // 如果 danjia 里面保存的值为 "单价",执行下面语句时会崩溃
let price = Int(danjia)!
5. Implicitly unwrapped optionals(隐式解包) – test
即在声明变量时在类型后面加上!
,这样今后使用时就不必解包了,当成非可选项处理。
let age: Int! = nil
由于这样操作将视同为已经解包,所以不需要if let
和guard let
来处理。然而,如果真心没有值,是nil
的话,那程序就崩溃了。
隐式解包一般会用在这样的场景,某个变量开始没有值,但一旦使用后就保证一直有值。这样因为保证有值,就可以少去使用if let
的麻烦。
6. Nil coalescing (nil 合并) – test
假设有下面这样一个函数
func username(for id: Int) -> String? {
if id == 1 {
return "Taylor Swift"
} else {
return nil
}
}
如果调用的参数 id 为 15(反正不是 1 就行),将返回 nil。可以使用一个默认值来代替 nil。使用下面的方法
let user = username(for: 15) ?? "无名氏"
双问号的意思就是如果函数返回 nil,就取双问号后面的值。
7. Optional chaining(可选项链) – test
Swift 提供了一种使用可选项的便捷方式:如果要访问 a.b.c.
并且其中 b
是可选项,那么可以使用可选链:a.b?.c
。
程序运行时,Swift 先检查 b 的值,如果是 nil,其他的就统统忽略,直接返回 nil;而如果有值则会按部就班地解包、执行。
let names = ["John", "Paul", "George", "Ringo"] // Swift 自动推断为字符串数组
let beatle = names.first?.uppercased() // 因为第一个元素有值,所以 beatle 的值为 “JOHN”
let names2: [String] = [] // 声明空数组时必须指明元素类型
let beatle2 = names2.first?.uppercased() // 此时 first 不存在,为 nil,所以 beatle2 的值为 nil
8. Optional try – test
回忆一下第 5 天(Day 5)时曾经写过下面的程序。
我们将编写一个检查密码是否正确的函数,因此,如果用户尝试使用明显的密码,则会抛出错误:
enum PasswordError: Error { // 自定义的错误,类型必须是 Error
case obvious
}
func checkPassword(_ password: String) throws -> Bool { // 遇到错误会抛出的函数,使用 throws 关键字定义
if password == "password" {
throw PasswordError.obvious // 当 password 的值为 "password" 时,使用 throw 关键字抛出错误
}
return true
}
// 使用 `do...try...catch`捕捉错误。
do {
try checkPassword("password") // 尝试调用函数
print("一切正常") // 没有遇到错误
} catch {
print("对不起,出错了~~") // 遇到错误
}
这里可以有两种方法替换 try。第一种,使用 if let
。
if let result = try? checkPassword("password") {
print("结果是 \(result)")
} else {
print("完蛋,出错了。")
}
第二种,在确认有值的时候使用 try!
,但是如果没有值,程序可不会报错而是直接崩溃。
try! checkPassword("sekrit")
print("OK!")
9. Failable initializers – test
在写强制解包的时候,使用过下面的代码
let str = "5"
let num = Int(str)
这里将 str 转换成 Int 类型,但是因为 str 可以是任何东西,所以实际上返回的是一个可选的整数。这就是 failable initializer。
可以在自定义的结构体或者类中使用 init?() 来替代init(),如果有问题就返回nil。返回值就会成为期待类型的可选项,供解包用。
如,Person 结构体需要使用一个 9 个字母长的 id,如果不是 9 个字母就返回 nil,否则程序正常进行。
struct Person {
var id: String
init?(id: String) {
if id.count == 9 {
self.id = id
} else {
return nil
}
}
}
10. Typecasting(类型转换) – test
Swift 总是要清楚每个变量的类型,但是有时候我们了解的要比 Swift 多一些。如下面三个类:
class Animal { }
class Fish: Animal { }
class Dog: Animal {
func makeNoise() {
print("Woof!")
}
}
这里 Fish 和 Dog 都继承 Animal,所以我们可以创建下面的数组
let pets = [Fish(), Dog(), Fish(), Dog()]
那么 pets 的类型被推断为 Animal。如果遍历 pets 时遇到 Dog 调用 makeNoise 函数,就要使用关键字 as? 来返回一个可选项,这个可选项在类型转换失败的时候返回 nil。
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
11. Optionals summary(可选项小结) – test
- 可选项用于以清晰明确的方式表示值的缺失。
- Swift不会允许在不解开可选参数的情况下使用
if let
或使用guard let
。- 可以使用感叹号强制打开可选的选项,但是如果强制打开
nil
的代码,则会崩溃。- 隐式解包没有常规可选项的安全检查。
- 可以使用 nil 合并来解开可选值,如果其中没有任何内容,则提供默认值。关键字是 ??
- 可选链可以用来操作可选控件,但是如果可选控件结果为空,则代码直接返回 nil,其他部分被忽略。
- 您可以使用
try?
将throwing函数转换为可选的返回值,但是如果返回值为 nil 时程序会崩溃。- 如果在初始化输入错误时需要初始化失败时返回 nil,可以使用
init?()
替代init()
。- 可以使用类型转换将一种类型的对象转换为另一种类型