空引用(字面意义上是变量没有值)是1965年托尼·霍尔(Tony Hoare)发明的。回想起有关空引用的问题,他说:“我称之为十亿美元的错误”,因为它们导致了很多问题。

它专门用于Swift的null引用解决方案(称为optionals)的解决方案。

本质上,一个可选的方法试图回答“如果我们的变量没有值该怎么办?”这个问题,Swift希望确保我们所有的程序都尽可能安全,因此它有一些非常具体且非常重要的功能!–处理这种可能性的技术。

创建一个为空的变量

我们可以让变量为可选项,通过类型后添加?。如果想要创建一个空的变量,我们还需要让变量等于nil,例如:

1
var build: Int? = nil

分别处理空变量和有值变量(解包可选项)

我们可以使用if let来处理空变量和有值变量。例如:

1
var name: String? = nil

如果使用该name.count怎么办?真正的字符串具有一个count属性,该属性存储它具有多少个字母,但这是nil–它是空,不是字符串,因此没有count

因此,尝试读取name.count是不安全的,Swift不允许这样做。取而代之的是,我们必须查看可选的内容并查看其中的内容-这个过程称为unwrapping

展开可选内容的一种常见方法是使用if let语法,该语法使用条件进行展开。如果可选变量中有一个值,则可以使用它,但是如果没有,则条件失败。

例如:

1
2
3
4
5
if let unwrapped = name {
print("\(unwrapped.count) letters")
} else {
print("Missing name.")
}

如果name持有一个字符串,它将被正常放入,我们可以count在条件中读取它的属性。或者,如果name为空,则else代码将运行。

注意:
不要对非可选项的变量使用if let语法
可选项的变量要使用if let语法
if let需要将可选内容绑定到一个新的名称,所以需要if let a=b{}这个格式

其他例子:

1
2
3
4
5
let album = "Red"
let albums = ["Reputation", "Red", "1989"]
if let position = albums.firstIndex(of: album) {
print("Found \(album) at position \(position).")
}

其他例子:

1
2
3
4
5
6
var weatherForecast: String? = "sunny"
if let forecast = weatherForecast {
print("The forecast is \(forecast).")
} else {
print("No forecast available.")
}

可选值不能直接比较,下面的代码是错误的

1
2
3
4
5
6
7
var bestScore: Int? = nil
bestScore = 101
if bestScore > 100 {
print("You got a high score!")
} else {
print("Better luck next time.")
}

**下面的代码是正确的:

1
2
3
4
5
6
7
8
9
var bestScore: Int? = nil
bestScore = 101
if let bestScore = bestScore{
if bestScore > 100 {
print("You got a high score!")
} else {
print("Better luck next time.")
}
}

在构建时考虑解包

if let有一种替代方法是guard letguard展开后跟随的else表示为空时所经历的事件。

if letguard let之间的区别就是在if let中创建的解包后的变量只能在if let中使用,而guard let创建的解包后的变量在guard let外面也依然可用。因为可选值在guard let完成后会留在原处,所以我们可以在函数末尾打印未封装的字符串。例如:

1
2
3
4
5
6
7
8
9
10
11
func greet(_ name: String?) {
guard let unwrapped = name else {
print("You didn't provide a name!")
return
}

print("Hello, \(unwrapped)!")
}

greet("Bob")
greet(nil)

使用guard let,你可以在功能开始时处理问题,然后立即退出。这意味着函数的其余部分是完美的路径-如果一切正确,代码将采用的路径。

而使用if let就需要将print放入到if let之中:

1
2
3
4
5
6
7
8
9
10
11
12
func greet(_ name: String?) {
if let unwrapped = name{
print("Hello, \(unwrapped)!")
} else {
print("You didn't provide a name!")
return
}

}

greet("Bob")
greet(nil)

注意:
if letguard let还有一点不同,guard let必须直接跟随else
guard let必须在最后有一个return,即使没有返回值。有返回值可以直接return需要的返回值,没有返回值直接在guard letelse{}中的最后部分写return

其他例子:

1
2
3
4
5
6
7
8
9
10
func double(number: Int?) -> Int? {
guard let number = number else {
return nil
}
return number * 2
}
let input = 5
if let doubled = double(number: input) {
print("\(input) doubled is \(doubled).")
}

其他例子:

1
2
3
4
5
6
7
8
9
func uppercase(string: String?) -> String? {
guard let string = string else {
return nil
}
return string.uppercased()
}
if let result = uppercase(string: "Hello") {
print(result)
}

其他例子:

1
2
3
4
5
6
7
8
9
func describe(occupation: String?) {
guard let occupation = occupation else {
print("You don't have a job.")
return
}
print("You are an \(occupation).")
}
let job = "engineer"
describe(occupation: job)

强制解包可选值

可选参数表示可能存在或可能不存在的数据,但是有时你可以确定一个值不是nil。在这些情况下,Swift可让你强制打开可选的包装:将其从可选类型转换为非可选类型。

例如,如果你的字符串包含数字,则可以将其转换为Int如下形式:

1
2
let str = "5"
let num = Int(str)

num是可选的, Int因为你可能已尝试转换“ Fish”而不是“ 5”之类的字符串。

即使Swift不确定转换是否会起作用,你也可以看到代码是安全的,因此可以通过编写!来强制展开结果Int(str),如下所示:

1
let num = Int(str)!

Swift会立即解开可选项,并使其num成为常规Int而不是Int?。但是,如果你错了 -如果str某些东西无法转换为整数-你的代码将崩溃。

结果,只有在确定它是安全的时才应强制打开包装。

例如:

1
2
let str = "fish"
let num = Int(str)

num得到的结果为nil

其他例子:

1
2
3
4
5
6
7
8
9
10
11
12
func title(for page: Int) -> String? {
guard page >= 1 else {
return nil
}
let pageCount = 132
if page < pageCount {
return "Page \(page)"
} else {
return nil
}
}
let pageTitle = title(for: 16)!

隐式解包

与常规的可选内容一样,隐式解包的可选内容可能包含一个值,也可能是nil。但是,与常规的可选选项不同,你不需要为使用它们而将它们拆开:你可以像完全不是可选的那样使用它们。

通过在类型名称后添加感叹号来创建隐式解包的可选内容,如下所示:

1
let age: Int![]= nil

因为它们的行为就好像它们已经被解开,所以你不需要if letguard let使用隐式解开的可选对象。但是,如果你尝试使用它们,而它们没有任何价值(如果确实如此),则nil你的代码将崩溃。

隐式地存在未包装的可选内容,因为有时变量会以nil开头,但在使用前始终具有值。因为你知道它们会在你需要它们的时候就具有价值,所以不必一直写会很有帮助的if let

话虽这么说,但是如果你能够使用常规的可选参数,那通常是个好主意。

合并(对于没有值的判断并给予默认值)

我们有的时候会让一个函数返回空值,但这对创建变量是不利的。但是我们可以对空值进行判断:

1
2
3
4
5
6
7
func username(for id: Int) -> String? {
if id == 1 {
return "Taylor Swift"
} else {
return nil
}
}

这是一个正常的函数,它在id = 15时会返回空值,我们可以用??为这种情况提供一个默认值(使用??的过程称之为合并:

1
let user = username(for: 15) ?? "Anonymous"

这将检查从username()函数返回的结果:如果是字符串,则将其解包并放入user,但是如果是nil,则将改用“Anonymous”。

注意:
不能合并非可选值
不能合并不同类型的值

其他例子:

1
2
var userOptedIn: Bool? = nil
var optedIn = userOptedIn ?? false

可选链接

如果格式为a.b.c,并且b为可选项,那么我们可以在b后面添加?。如果b返回值为nil,那么将会忽略其他所有值,直接设置为nil;如果不是nil则解包并正常运行。例如:

1
let names = ["John", "Paul", "George", "Ringo"]

我们将使用数组的属性first,如果不为nil,它将返回名字。然后,我们可以调用uppercased()结果使其成为大写字符串:

1
let beatle = names.first?.uppercased()

这个问号是可选的链接-如果first返回nil则Swift不会尝试将其大写,而是立即设置beatlenil

注意:
因为数组是可选的,所以在使用firstlast等调用是必须使用?来展开

其他例子:

1
2
let songs: [String]? = [String]()
let finalSong = songs?.last?.uppercased()

其他例子:

1
2
let shoppingList = ["eggs", "tomatoes", "grapes"]
let firstItem = shoppingList.first?.appending(" are on my shopping list")

其他例子:

1
2
let capitals = ["Scotland": "Edinburgh", "Wales": "Cardiff"]
let scottishCapital = capitals["Scotland"]?.uppercased()

其他例子:

1
2
let opposites = ["hot": "cold", "near": "far"]
let oppositeOfLight = opposites["light"].uppercased()

相关阅读:数组的属性和方法

可选尝试

相关阅读:编写投掷函数

回到我们谈论抛出函数的时候,我们看了下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.")
}

运行投掷功能,使用dotry以及catch优雅地处理错误。

有两种替代方法try,既然你了解可选方法并强制展开,则这两种方法都会更有意义。

第一个是try?,并将throwing函数更改为返回可选函数的函数。如果函数抛出错误,则将nil结果发送给你,否则,你将获得返回值,并将其包装为可选值。

使用try?我们可以checkPassword()像这样运行:

1
2
3
4
5
if let result = try? checkPassword("password") {
print("Result was \(result)")
} else {
print("D'oh.")
}

另一种选择是try!,你可以在确定函数不会失败时使用它。如果该函数确实抛出错误,则你的代码将崩溃。

使用try!我们可以将代码重写为:

1
2
try![]checkPassword("sekrit")
print("OK!")

可选的初始化容器

在谈论强制展开时,我使用了以下代码:

1
2
let str = "5"
let num = Int(str)

它将字符串转换为整数,但是由于你可能尝试传递任何字符串,因此你实际上返回的是一个可选整数。

这是一个失败的初始化程序:可能有效或无效的初始化程序。你可以使用init?()而不是init(),在自己的结构和类中编写这些代码,并nil在出现问题时返回。然后,返回值将是你的类型的可选值,以便你随意展开。

例如,我们可以编写一个Person必须使用9个字母的ID字符串创建的结构。如果使用了除9个字母的字符串以外的其他任何字符,我们将返回nil,否则我们将照常继续。

这是Swift中的内容:

1
2
3
4
5
6
7
8
9
10
11
struct Person {
var id: String

init?(id: String) {
if id.count == 9 {
self.id = id
} else {
return nil
}
}
}

类型转换

Swift必须始终知道每个变量的类型,但是有时你比Swift知道更多的信息。例如,这是三个类:

1
2
3
4
5
6
7
8
class Animal { }
class Fish: Animal { }

class Dog: Animal {
func makeNoise() {
print("Woof!")
}
}

我们可以创建几条鱼和几条狗,并将它们放入数组中,如下所示:

1
let pets = [Fish(), Dog(), Fish(), Dog()]

Swift可以看到两者FishDogAnimal类继承,因此它使用类型推断来构成pets一个数组Animal

如果我们要遍历pets数组并要求所有的狗吠叫,则需要执行类型转换:Swift将检查每个宠物是否都是Dog对象,如果是,则可以调用makeNoise()

这使用了一个名为的新关键字as?,该关键字返回一个可选参数:nil如果类型转换失败,则为可选关键字,否则为转换后的类型。

这是我们在Swift中编写循环的方式:

1
2
3
4
5
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}

其他例子:

1
2
3
4
5
6
7
8
class Person {
var name = "Taylor Swift"
}
class User: Person { }
let taylor = User()
if let user = taylor as? User {
print("\(user.name) is a user.")
}

其他例子:

1
2
3
4
let flavor = "apple and mango"
if let taste = flavor as? String {
print("We added \(taste).")
}

其他例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Transport { }
class Train: Transport {
var type = "public"
}
class Car: Transport {
var type = "private"
}
let travelPlans = [Train(), Car(), Train()]
for plan in travelPlans {
if let train = plan as? Train {
print("We're taking \(train.type) transport.")
}
}

其他例子:

1
2
3
4
5
6
7
8
9
10
11
12
class Bird {
var wingspan: Double? = nil
}
class Eagle: Bird { }
let bird = Eagle()
if let eagle = bird as? Eagle {
if let wingspan = eagle.wingspan {
print("The wingspan is \(wingspan).")
} else {
print("This bird has an unknown wingspan.")
}
}

总结

  • 可选选项使我们以清晰明确的方式表示值的缺失。
  • Swift不会允许我们使用if let或使用而不拆开可选的选项guard let。
  • 你可以使用感叹号强制打开可选选项,但是如果尝试强制打开nil代码,则代码将崩溃。
  • 隐式展开的选件没有常规选件的安全检查。
  • 你可以使用nil合并来解开可选值,如果其中没有任何内容,则提供默认值。
  • 可选链接使我们可以编写代码来操作可选对象,但是如果可选对象结果为空,则代码将被忽略。
  • 你可以try?用来将throwing函数转换为可选的返回值,或者try!在抛出错误时崩溃。
  • 如果你在初始化输入不正确时需要初始化失败,请使用init?()创建失败的初始化。
  • 你可以使用类型转换将一种类型的对象转换为另一种类型。

一些例子:

1
2
3
4
5
6
7
8
9
10
11
struct Dog {
var name: String
init?(name: String) {
guard name == "Lassie" else {
print("Sorry, wrong dog!")
return nil
}
self.name = name
}
}
let dog = Dog(name: "Fido")

参考资料

查看下一天的SwiftUI学习笔记

关于100days英文课程