处理丢失的数据
我们使用了诸如Int
这样的类型来保存诸如5之类的值。但是,如果您想为用户存储属性age
,但是您不知道某人的年龄,该怎么办?
你可能会说“好吧,我会存储0”,但这样你就会混淆新生婴儿和年龄你不知道的人。你可以用一个特殊的数字,比如1000或-1来表示“未知”,这两个数字都是不可能存在的年龄,但是你真的会记得在所有使用过的地方都有这个数字吗?
Swift的解决方案称为optionals
,您可以使任意类型的options
。一个可选的整数可能有一个像0或40这样的数字,但它可能根本没有值nil
-它可能确实不存在,在Swift中是这样。
要使类型为可选,请在其后添加问号。例如,我们可以这样创建一个可选的整数:
var age: Int? = nil
那没有任何数字-它没有任何东西。但是,如果我们以后知道那个年龄,我们可以使用它:
age = 38
解开可选项
可选字符串可能包含“ Hello”之类的字符串,也可能为nil
-完全没有。
考虑以下可选字符串:
var name: String? = nil
如果使用name.count
怎么办?真正的字符串具有一个count
属性,该属性存储它具有多少个字母,但这是nil
–它是空的内存,不是字符串,因此没有count
。
因此,尝试读取name.count
是不安全的,Swift不允许这样做。取而代之的是,我们必须查看可选的内容并查看其中的内容-这个过程称为unwrapping。
展开可选内容的一种常见方法是使用if let
语法,该语法使用条件进行展开。如果可选变量中有一个值,则可以使用它,但是如果没有,则条件失败。
例如:
if let unwrapped = name {
print("\(unwrapped.count) letters")
} else {
print("Missing name.")
}
如果包含一个字符串name
,它将作为常规字符串String
放在unwrapped
中,我们可以在条件中读取它的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?。但是,如果您错了 -如果str
某些东西无法转换为整数-您的代码将崩溃。
结果,只有在确定它是安全的时才应强制打开包装–这是通常将其称为崩溃操作符的原因。
隐式展开的可选内容
与常规的可选内容一样,隐式解包的可选内容可能包含一个值,也可能是nil
。但是,与常规的可选选项不同,您不需要为使用它们而将它们拆开:您可以像完全不是可选的那样使用它们。
通过在类型名称后添加感叹号来创建隐式解包的可选内容,如下所示:
let age: Int! = nil
因为它们的行为就好像它们已经被解开,所以您不需要if let
或guard let
使用隐式解开的可选对象。但是,如果您尝试使用它们,而它们没有任何价值nil
(如果确实如此),则您的代码将崩溃。
隐式展开的选项存在,因为有时变量将以nil
开始,但在需要使用它之前将始终有一个值。因为你知道它们在你需要的时候会有价值,所以不必一直写if let
是有帮助的。
话虽这么说,但是如果您能够使用常规的可选参数,那通常是个好主意。
Nil合并
nil合并运算符解开一个可选值,如果有则返回内部的值。如果没有值(如果可选为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
是可选项,则可以在其后写一个问号以启用可选链接:a.b?.c
。
运行该代码时,Swift将检查b
是否具有值,如果该值是nil
,则该行的其余部分将被忽略-Swift将立即返回nil
。但是,如果它具有值,它将被解包并继续执行。
要尝试此操作,请使用以下名称数组:
let names = ["John", "Paul", "George", "Ringo"]
我们将使用该数组的first
属性,如果数组为空,它将返回第一个名字。然后,我们可以调用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?
,并将throwing
函数更改为返回可选函数的函数。如果函数抛出错误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
必须使用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!")
}
}
我们可以创建几条鱼和几条狗,并将它们放入数组中,如下所示:
let pets = [Fish(), Dog(), Fish(), Dog()]
Swift可以看到Fish
和Dog
两者从Animal
类继承,因此它使用类型推断Animal来构成一个pets
数组。
如果我们要遍历pets
数组并要求所有的狗吠叫,则需要执行类型转换:Swift将检查每个宠物是否都是Dog
对象,如果是,则可以调用makeNoise()
。
这使用了一个名为的新关键字as?
,该关键字返回一个可选参数:如果类型转换失败nil
,则为可选关键字,否则为转换后的类型。
这是我们在Swift中编写循环的方式:
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
总结
- 1.可选选项使我们以清晰明确的方式表示值的缺失。
- 2.Swift不会让我们在没有拆开包装的情况下使用选项,无论是使用
if let
还是使用guard let
。 - 3.您可以使用感叹号强制打开可选选项,但是如果尝试强制打开
nil
代码,则代码将崩溃。 - 4.隐式展开的选件没有常规选件的安全检查。
- 5.您可以使用
nil
合并来解开可选值,如果其中没有任何内容,则提供默认值。 - 6.可选链接使我们可以编写代码来操作可选对象,但是如果可选对象结果为空,则代码将被忽略。
- 7.您可以用
try?
来将throwing
函数转换为可选的返回值,或者try!
在抛出错误时崩溃。 - 8.如果您需要初始化程序在输入错误时失败,请使用
init?()
创建失败的初始化。 - 9.您可以使用类型转换将一种类型的对象转换为另一种类型。