文章目录
处理缺失数据
我们已经使用诸如 之类的类型Int来保存像 5 这样的值。但是如果您想存储age用户的属性,如果您不知道某人的年龄,您会怎么做?
您可能会说“好吧,我会存储 0”,但这样一来,您就会混淆新生婴儿和您不知道年龄的人。您可以使用特殊数字(例如 1000 或 -1)来表示“未知”,这两个年龄都是不可能的,但您真的会记住所有使用这个数字的地方吗?
Swift 的解决方案称为可选类型,你可以用任意类型创建可选类型。可选整数可能包含数字 0 或 40,但它可能根本没有值 - 它可能真的缺失了,这是nilSwift 中的情况。
要使类型可选,请在其后添加问号。例如,我们可以像这样创建一个可选整数:
var age: Int? = nil
这并不代表任何数字——它什么也不代表。但如果我们后来知道了年龄,我们可以使用它:
age = 38
展开可选值
可选字符串可能包含像“Hello”这样的字符串,或者可能为零——什么都没有。
考虑这个可选字符串:
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常规中String,我们可以count在条件中读取其属性。或者,如果name为空,else则将运行代码。
用保护装置解开
的另一种方法if let是guard let,它也可以解开可选项。guard let将为您解开可选项,但如果它nil在里面找到它,它会期望您退出使用它的函数、循环或条件。
if let但是,和之间的主要区别guard let在于,解包的可选项在代码之后仍然可用guard。
让我们用一个函数来尝试一下greet()。它将接受一个可选字符串作为其唯一参数并尝试解包它,但如果里面没有任何内容,它将打印一条消息并退出。因为使用解包的可选值在完成guard let后仍然存在guard,所以我们可以在函数末尾打印解包的字符串:
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,因为您可能尝试转换像“Fish”而不是“5”这样的字符串。
尽管 Swift 不确定转换是否有效,但您可以看到代码是安全的,因此您可以通过!在之后写入来强制解开结果Int(str),如下所示:
let num = Int(str)!
Swift 会立即解开可选值并将其转换为num常规值Int,而不是Int?。但如果你错了——ifstr是无法转换为整数的东西——你的代码就会崩溃。
因此,只有当您确定它是安全的时候才应该强制解包 - 这就是它通常被称为崩溃操作员的原因。
隐式解包可选值
与常规可选项一样,隐式解包的可选项可能包含一个值,也可能是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因为无法识别该用户,但是使用 nil 合并,我们可以提供一个默认值"Anonymous",如下所示:
let user = username(for: 15) ?? "Anonymous"
这将检查从函数返回的结果username():如果它是一个字符串,那么它将被解开并放入user,但如果它有nil内部,那么将使用"Anonymous"
可选链式调用
Swift 为我们提供了使用可选变量的快捷方式:如果你想访问类似a.b.c和b可选的内容,你可以在它后面写一个问号以启用可选链接:a.b?.c。
当运行该代码时,Swift 将检查是否b有值,如果有,则nil该行的其余部分将被忽略 - Swift 将nil立即返回。但是如果它有值,它将被解包并继续执行。
为了尝试这一点,这里有一个名称数组:
let names = ["John", "Paul", "George", "Ringo"]
我们将使用该数组的属性,如果存在第一个名字或数组为空,first它将返回第一个名字。然后我们可以调用结果使其成为大写字符串:niluppercased()
let beatle = names.first?.uppercased()
那个问号是可选链接——如果first返回nil,那么 Swift 不会尝试将其大写,并且会立即设置beatle为nil。
可选尝试
当我们讨论抛出函数时,我们看了以下代码:
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?()而不是将这些初始化器写在您自己的结构和类中init(),如果出现错误则返回nil。返回值将是您类型的可选项,您可以随意解包。
例如,我们可以编写一个Person必须使用九个字母的 ID 字符串创建的结构。如果使用除九个字母字符串以外的任何其他字符串,我们将返回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!")
}
}
我们可以创建几条鱼和几条狗,并将它们放入一个数组中,如下所示:
let pets = [Fish(), Dog(), Fish(), Dog()]
Swift 可以看到Fish并Dog从类中继承Animal,因此它使用类型推断来创建pets一个数组Animal。
如果我们想要循环遍历pets数组并让所有的狗叫,我们需要执行类型转换:Swift 将检查每只宠物是否是一个Dog对象,如果是,那么我们就可以调用makeNoise()。
这使用了一个名为的新关键字as?,它返回一个可选项:nil如果类型转换失败则为,否则为转换后的类型。
以下是我们在 Swift 中编写循环的方法:
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
总结
- 可选项让我们能够以清晰明确的方式表示值的缺失。
- Swift 不允许我们在未解包的情况下使用可选项,无论是使用if let还是使用guard let。
- 您可以使用感叹号强制解开可选项,但如果您尝试强制解开,nil您的代码将会崩溃。
- 隐式解包的可选项没有常规可选项的安全检查。
- 您可以使用 nil 合并来解开可选项,如果其中没有任何内容,则提供默认值。
- 可选链接让我们可以编写代码来操作可选项,但如果可选项为空,则代码将被忽略。
- 您可以使用try?将抛出函数转换为可选的返回值,或者try!在抛出错误时崩溃。
- 如果您需要您的初始化程序在输入错误输入时失败,请使用init?()可失败的初始化程序。
- 您可以使用类型转换将一种类型的对象转换为另一种类型的对象。