处理缺失的数据
我们已经会使用 Int 这样的类型来存储像 5 这样的数值。不过,当你想要存储用户年龄这样的属性,并且你还不知道该用户的年龄时你该怎么做呢?
Swift的解决方案称为 optional,即可选型。你可以基于任意类型创建可选型。一个可选型整数可以有诸如1或者1000这样的数字,也可能没有任何值,即值可以缺失,Swift里表示缺失是用 nil。
为了把一个类型变成可选型,只需要在类型后面加一个问号(?)。举个例子,我们可以这样把一个整数变成可选型:
var age: Int? = nil
这个可选型现在并不保有任何整数,但是如果之后我们知道了年龄值,我们可以给它重新赋值。
age = 38
解包可选型
可选型字符串可能包含一个 “Hello” 或者 nil。
考虑这样一个可选字符串:
var name: String? = nil
当我们调用 name.count 时会发生什么呢?一个真的字符串有 count 属性,它存储字符串的字符数量。但它是 nil 时,并没有 count 属性。
因此,试图直接使用 name.count 是不安全的,Swift也不允许。取而代之的是,我们必须先检查可选型里面有些什么,这个过程被称为 解包 。
解包可选型的一种常见做法是采用 if let 语法,它是一种有条件的解包。如果可选型里有值,你就可以使用它,如果没有则条件失败。
举个例子:
if let unwrapped = name {
print("\(unwrapped.count) letters")
} else {
print("Missing name.")
}
如果 name 持有一个字符串,这个字符串会被放进一个叫 unwrapped 的常规字符串,然后我们就可以读取它的 count 属性。另一种情况,如果 name 是 nil, 那么 else 中的代码将被执行。
用guard解包可选型
if let 的一个替代方案是 guard let,后者也可以用来解包可选型。guard let 会为你解包,但是当它发现可选型里是 nil 时会期望你退出它所处的函数,循环或者条件。
因此,if let 和 guard let 的主要区别在于 guard let 之后可选型还可以继续使用。
让我们尝试一下 greet() 函数。它将接收一个可选字符串作为唯一的参数,当它解包发现这个参数是nil时会打印消息并且退出函数。因为可选型 unwrapped 在 guard let 的语句块结束之后作用,我们可以在函数最后打印这个解包后的字符串。
func greet(_ name: String?) {
guard let unwrapped = name else {
print("You didn't provide a name!")
return
}
print("Hello, \(unwrapped)!")
}
通过使用 guard let,你可以在函数的前面解决可选型的解包问题,在这之后的代码就可以愉快地使用解包后的值而不必担心出错了。
强制解包
可选型代表可能存在也可能不存在的数据,而某些时候你是 确切 知道一个可选型的值并不是nil。在这种情况下,Swift允许你强制解包这个可选型:通过把一个可选型转换为非可选型的方式。
举个例子,下面你试图将一个字符串转换成 Int,就像这样:
let str = "5"let num = Int(str)
上面的代码中 num 其实是一个可选型 Int。因为你有可能尝试转换一个不是数字的字符串导致转换失败。
尽管Swift不确定转换能否成功,你自己是知道你的结果是否可以安全地执行强制解包。这个强制解包是通过在Int(str) 之后添加 !来完成的。
let num = Int(str)!
通过强制解包,Swift将立刻解包 num,把它变成一个常规的 Int 而不是 Int?。但是假如你是错的,比方说 str是某个不能转换成数字的字符串,那么你的代码将崩溃。
强制解包的操作符常常被戏称为崩溃操作符。因此,只有当你确信强制解包是安全的你才能这么做。
隐式解包可选型
像常规可选型一样,隐式解包可选型可能包含一个值也可能包含 nil。不过,跟常规可选型不一样的是,隐式解包可选型时不需要再解包,你可以把它们当做非可选型一样使用。
隐式解包可选型是通过类型名之后添加感叹号来声明的,就像这样:
let age: Int! = nil
由于它们表现起来就像已经被解包过的样子,你并不需要 if let 或者 guard let 这样的代码来测试它们。不过,当你试图使用它们而它们实际上没有值时,即它们的值是 nil时,你的代码将崩溃。
隐式解包可选型存在的意义在于,有些情况某个变量一开始时是nil,但当你需要用到它之前它总会有值。如果你可以确信这一点,那么采用隐式解包可选型可以省去一直写 if let 的麻烦。
值得一提的是,如果条件允许采用常规可选型,安全起见最好总是采用常规可选型。
空合运算符
空合运算符解包一个可选型,如果可选型包含值则返回这个值,如果可选型不包含值,即可选型的值是 nil,那么返回某个默认值。两种情况,返回值都不再是可选型:它要么是可选型里包含的有效值,要么是一个备选的默认值。
下面是一个接收整数作为唯一参数并且返回可选型字符串的函数:
func username(for id: Int) -> String? {
if id == 1 {
return "Taylor Swift"
} else {
return nil
}
}
如果我们以ID 15来调用这个函数,我们将得到 nil。通过空合运算符,我们可以提供一个叫 “Anonymous” 的默认值,就像这样:
let user = username(for: 15) ?? "Anonymous"复制代码
它将检查 username() 函数返回的值:如果是一个字符串,它将被解包并放入 user,如果是 nil,则使用 “Anonymous” 替代。
可选链
在使用可选型时,Swift为我们提供了一种快捷方式:假如你试图访问形
如 a.b.c 这样的代码并且 b 是可选型,你可以在 b 后面写一个问号来启用 可选链: a.b?.c。
当代码运行时,Swift会检查 b 是否有值,如果它是 nil,那么这行代码剩下的部分将被忽略。Swift会立即返回 nil。但是如果 b 有值,它将被解包,代码执行将继续。
下面是一个名字的数组:
let names = ["John", "Paul", "George", "Ringo"]
我们将使用数组的 first 属性,如果数组里第一个名字有值的话返回这个名字,如果数组为空则返回 nil。在结果之上,我们调用 uppercased() 把它变成一个全大写的字符串:
let beatle = names.first?.uppercased()
上面的问号就是可选链。如果 first 返回 nil,那么Swift就不会尝试将它转换成全大写,它会将 beatle 立即设置为 nil。
可选型 try
看下面的代码:
enum PasswordError: Error {
case obvious
}
func checkPassword(_ password: String) throws -> Bool {
if password == "password" {
throw PasswordError.obvious
}
return true
}
do {
try checkPassword("password")
print("That password is good!")
} catch {
print("You can't use that password.")
}
通过 do, try 和 catch,我们得以运行可能抛出错误的函数,并且优雅地处理错误。
上面的 try 写法其实有另外两种选择,这两种选项都能加深你对可选型和强制解包的理解。
第一个是 try?,它将可能抛出错误的函数转换成返回可选型的函数。如果函数抛出错误,那你就会得到 nil作为函数的执行结果,否则你会得到将返回值包装之后的可选型。
尝试使用 try? 来执行 checkPassword(),像下面这样:
if let result = try? checkPassword("password") {
print("Result was \(result)")
} else {
print("D'oh.")
}
另外一种选择是 try!,当你确信函数一定不会失败时你可以采用它。如果函数实际抛出了错误,你的代码将崩溃。
使用 try! 来重写前面的代码:
try! checkPassword("sekrit")print("OK!")
失败构造器
当我们说到强制解包的时候,我用了下面的代码:
let str = "5"
let num = Int(str)
它将一个字符串转换成一个整数,但由于你可以传入任意字符串,你得到的实际上是一个可选型整数。
这里用到一种叫做 失败构造器 的东西:它是一种可能成功也可能失败的构造器。你在结构体或者类里面用 init?() 来实现失败构造器。如果某些东西出错,它将返回 nil。因此这种构造器返回的是某种类型的可选型,你用之前需要解包。
举个例子,我们现在要求 Person 结构体必须通过一个9字符的ID字符串来构造。只要不是9个字符,都会返回 nil。
Swift 代码如下:
struct Person {
var id: String
init?(id: String) {
if id.count == 9 {
self.id = id
} else {
return nil
}
}
}
类型转换
Swift 知道每个变量的类型,但有的时候你知道的信息比Swift更多。举个例子,这里有三个类:
class Animal { }class Fish: Animal { }class Dog: Animal {
func makeNoise() {
print("Woof!")
}
}
我们创建几个Fish和几个Dog,然后把它们放进一个数组,像下面这样:
let pets = [Fish(), Dog(), Fish(), Dog()]
Swift 知道 Fish 和 Dog 都继承自 Animal 类,因此它通过类型推断将 pets 创建为一个 Animal 类型的数组。
如果我们想遍历 pets 数组,让所有的狗发出叫声,我们需要执行一次类型转换:Swift将检查每个pet是否 Dog对象,以便我们调用 makeNoise() 方法。
这里用到了一个关键字 as?,它将返回一个可选型:类型转换失败时返回 nil,成功则返回转换后的类型。
Swift代码如下:
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
总结
让我们来总结一下。
网友评论