经过前面大约14章的学习,我们其实已经掌握了Swift的绝大部份基础知识。接下来我们就可以开始主攻SwiftUI了,但在这之前,我想我们有必要再把我们学习过的Swift内容复习一遍,这样我们才能更好的开始SwiftUI。那,现在就让我们开始吧!

创建常量(constants)和变量(variables)

Swift 可以创建常量和变量,但通常更倾向于创建常量。

用它来创建和更改变量字符串:

var name = "Ted"
name = "Rebecca"

如果不想改变数值,可以使用常量来代替:

let user = "Daphne"

print() 函数有助于学习和调试,并能显示变量的一些信息:

print(user)

字符串 (Strings)

Swift 字符串以双引号开始和结束:

let actor = "Tom Cruise"

它们与表情符号也配合得很好:

let actor = "Tom Cruise 🏃‍♂️"

如果你希望在字符串中使用双引号,请在它们前面放置一个反斜杠:

let quote = "He tapped a sign saying \"Believe\" and walked away."

如果你想要一个跨越多行的字符串,请以三个双引号开头和结尾,如下所示:

let movie = """
A day in
the life of an
Apple engineer
"""

Swift为字符串提供了许多有用的属性和方法,包括**.count**来读取它有多少个字母:

print(actor.count)

还有**hasPrefix()hasSuffix()**让我们知道字符串是以特定字母开头还是结尾:

print(quote.hasPrefix("He"))
print(quote.hasSuffix("Away."))

**提示:**在Swift中,字符串区分大小写,因此第二次检查将返回false。

整数 (Int)

Swift使用**Int**类型存储整数,该类型支持一系列标准数学运算符:

let score = 10
let higherScore = score + 10
let halvedScore = score / 2

它还支持修改原位变量的复合赋值运算符:

var counter = 10
counter += 5

整数自己具有一些有用的功能,例如**isMultiple(of:)**方法:

let number = 120
print(number.isMultiple(of: 3))

你还可以在特定范围内生成随机整数,如下:

let id = Int.random(in: 1...1000)

小数 (Decimals)

如果你创建一个具有小数点的数字,Swift会将其视为**Double**:

let score = 3.0

Swift认为**Double是与Int**完全不同的数据类型,不会让你将它们混合在一起。

布尔值 (Booleans)

Swift使用**Bool**类型来存储真或假:

let goodDogs = true
let gameOver = false

你可以通过调用其**toggle()**方法将布尔值从true翻转到false:

var isSaved = false
isSaved.toggle()

连接字符串

你可以使用字符串插值从其他数据中创建字符串:在字符串中写一个反斜杠,然后将变量或常量的名称放在括号中,如下所示:

let name = "Taylor"
let age = 26
let message = "I'm \(name) and I'm \(age) years old."
print(message)

当该代码运行时,它会打印“I’m Taylor and I’m 26 years old.”

数组 (Arrays)

你可以将项目分组到这样的数组中:

var colors = ["Red", "Green", "Blue"]
let numbers = [4, 8, 15, 16]
var readings = [0.1, 0.5, 0.8]

每一个都包含不同类型的数据:一个字符串、一个整数和一个小数。当我们从数组中读取数据时,我们将得到适当的类型-StringIntDouble

print(colors[0])
print(readings[2])

**提示:**确保项目存在于你请求的索引中,否则您的代码将崩溃——你的应用程序将停止工作。

如果你的数组是可变的,您可以使用**append()**添加新项目:

colors.append("Tartan")

你添加的数据类型必须与已经存在的数据类型相匹配。

数组具有有用的功能,例如**.count来读取数组中有多少个项目,或者remove(at:)**来删除特定索引中的一个项目:

colors.remove(at: 0)
print(colors.count)

你可以使用**contains()**来检查数组是否包含特定项目,如下所在:

print(colors.contains("Octarine"))

字典 (Dictionaries)

字典根据我们指定的键存储多个值。例如,我们可以创建一个字典来存储有关一个人的信息:

let employee = [
    "name": "Taylor",
    "job": "Singer"
]

要从字典中读取数据,请使用创建时使用的相同键:

print(employee["name", default: "Unknown"])
print(employee["job", default: "Unknown"])

如果我们请求的密钥不存在,将使用**default**值。

集合 (Sets)

集合类似于数组,只是你不能添加重复的项目,而且它们不会按照特定顺序存储项目。

这就成了一组数字:

var numbers = Set([1, 1, 3, 5, 7])
print(numbers)

请记住,该集合将忽略重复值,并且不会记住数组中使用的顺序。

将项目添加到集合中是通过调用其**insert()**方法完成的,如下所示:

numbers.insert(10)

与数组相比,集合有一个很大的优势:在集合上使用**contains()**实际上是即时的,无论集合包含多少项——即使是包含10,000,000项的集合也会立即响应。

枚举 (Enums)

枚举是一组命名值,我们可以创建并使用它来使我们的代码更高效、更安全。例如,我们可以像这样对工作日进行枚举:

enum Weekday {
    case monday, tuesday, wednesday, thursday, friday
}

这称为新的枚举**Weekday**,并提供五个案例来处理五个工作日。

我们现在可以制作该枚举的实例,然后为其分配其他可能的案例:

var day = Weekday.monday
day = .friday

类型注释 (Type annotations)

你可以通过使用如下类型注释来尝试为新变量或常量强制特定类型:

var score: Double = 0

如果没有**: Double部分,Swift会推断这是一个Int,但我们覆盖了它,并说它是一个Double**。

以下是一些基于迄今为止涵盖的类型的类型注释:

let player: String = "Roy"
var luckyNumber: Int = 13
let pi: Double = 3.141
var isEnabled: Bool = true
var albums: Array<String> = ["Red", "Fearless"]
var user: Dictionary<String, String> = ["id": "@twostraws"]
var books: Set<String> = Set(["The Bluest Eye", "Foundation"])

数组和字典非常普遍,它们具有更易于编写的特殊语法:

var albums: [String] = ["Red", "Fearless"]
var user: [String: String] = ["id": "@twostraws"]

知道确切的事物类型对于创建空集合很重要。例如,两者都创建空字符串数组:

var teams: [String] = [String]()
var clues = [String]()

枚举的值与枚举本身的类型相同,因此我们可以这样写:

enum UIStyle {
    case light, dark, system
}

var style: UIStyle = .light

条件 (Conditions)

使用**ifelseelse if**语句来检查条件并酌情运行一些代码:

let age = 16

if age < 12 {
    print("You can't vote")
} else if age < 18 {
    print("You can vote soon.")
} else {
    print("You can vote now.")
}

我们可以使用**&&**来组合两个条件,只有当里面的两个部分为真时,整个条件才会为真:

let temp = 26

if temp > 20 && temp < 30 {
    print("It's a nice day.")
}

或者,如果任一子条件为真,**||**将使条件为真。

switch语句

Swift允许我们使用**switch/case**语法根据多个条件检查值,如下所示:

enum Weather {
    case sun, rain, wind
}

let forecast = Weather.sun

switch forecast {
case .sun:
    print("A nice day.")
case .rain:
    print("Pack an umbrella.")
default:
    print("Should be okay.")
}

**switch**语句必须详尽无遗:必须处理所有可能的值,这样你就不会意外错过一个。

三元条件运算符 (The ternary conditional operator)

三元运算符允许我们检查一个条件并返回两个值之一:如果条件为真,则返回一个值,如果条件为假,则返回第二个值:

let age = 18
let canVote = age >= 18 ? "Yes" : "No"

当该代码运行时,**canVote将设置为“是”,因为age**设置为18岁。

循环 (Loops)

Swift的**for**循环为集合或自定义范围内的每个项目运行一些代码。例如:

let platforms = ["iOS", "macOS", "tvOS", "watchOS"]

for os in platforms {
    print("Swift works on \(os).")
}

你还可以循环一系列数字:

for i in 1...12 {
    print("5 x \(i) is \(5 * i)")
}

**1...12包含1到12(含)的值。如果您想排除最终数字,请使用..<**代替:

for i in 1..<13 {
    print("5 x \(i) is \(5 * i)")
}

提示:如果你不需要循环变量,请使用_

var lyric = "Haters gonna"

for _ in 1...5 {
    lyric += " hate"
}

print(lyric)

还有**while**循环,执行循环主体,直到条件为false,如下:

var count = 10

while count > 0 {
    print("\(count)…")
    count -= 1
}

print("Go!")

你可以使用**continue**”跳过当前循环迭代,然后继续执行以下操作:

let files = ["me.jpg", "work.txt", "sophie.jpg"]

for file in files {
    if file.hasSuffix(".jpg") == false {
        continue
    }

    print("Found picture: \(file)")
}

或者,使用**break**退出循环并跳过所有剩余的迭代。

函数 (Functions)

要创建新函数,请写上**func**然后加上函数名称,然后在括号内添加参数:

func printTimesTables(number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(number: 5)

我们需要在调用站点上写出**number: 5**,因为参数名称是函数调用的一部分。

要从函数中返回数据,请告诉Swift它是什么类型,然后使用**return**关键字将其发送回。例如,这返回一个掷骰子的结果:

func rollDice() -> Int {
    return Int.random(in: 1...6)
}

let result = rollDice()
print(result)

如果你的函数只包含一行代码,您可以删除**return**关键字:

func rollDice() -> Int {
    Int.random(in: 1...6)
}

从函数中返回多个值

元组存储了固定数量的特定类型的值,这是一种从函数返回多个值的便捷方法:

func getUser() -> (firstName: String, lastName: String) {
    (firstName: "Taylor", lastName: "Swift")
}

let user = getUser()
print("Name: \(user.firstName) \(user.lastName)")

如果你不需要元组中的所有值,你可以重组元组,将其拆分成单个值,然后用”_”告诉 Swift 忽略某些值:

let (firstName, _) = getUser()
print("Name: \(firstName)")

自定义参数标签 (Customizing parameter labels)

如果你在调用函数时不想传递参数的名称,请在它前面放置下划线:

func isUppercase(_ string: String) -> Bool {
    string == string.uppercased()
}

let string = "HELLO, WORLD"
let result = isUppercase(string)

另一种选择是在名字之前给它起另外一个名字:一个用于外部,一个用于内部:

func printTimesTables(for number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(for: 5)

在该代码中,**for外部使用,number**在内部使用。

为参数提供默认值

我们可以通过在类型后写一个等值来提供默认参数值,然后提供一个值,如下所示:

func greet(_ person: String, formal: Bool = false) {
	if formal {
        print("Welcome, \(person)!")
	  } else {
        print("Hi, \(person)!")
    }
}

现在我们可以用两种方式调用**greet()**):

greet("Tim", formal: true)
greet("Taylor")

处理函数中的错误

要处理函数中的错误,你需要告诉Swift哪些错误可能发生,编写一个可以抛出错误的函数,然后调用它并处理任何问题。

首先,定义可能出现的错误:

enum PasswordError: Error {
    case short, obvious
}

接下来,编写一个可以抛出错误的函数。这是通过将**throws加入函数类型,然后使用throw**来触发特定错误来完成的:

func checkPassword(_ password: String) throws -> String {
    if password.count < 5 {
        throw PasswordError.short
    }

    if password == "12345" {
        throw PasswordError.obvious
    }

    if password.count < 10 {
        return "OK"
    } else {
        return "Good"
    }
}

现在通过启动**do块来调用抛出函数,使用try**调用函数,然后捕捉发生的错误:

let string = "12345"

do {
    let result = try checkPassword(string)
    print("Rating: \(result)")
} catch PasswordError.obvious {
    print("I have the same combination on my luggage!")
} catch {
    print("There was an error.")
}

当涉及到捕捉错误时,你必须始终有一个可以处理各种错误的**catch**块。

闭包 (Closures)

你可以将函数直接分配给像这样的常量或变量:

let sayHello = {
    print("Hi there!")
}

sayHello()

在该代码中,**sayHello**是一个闭包——一块代码,我们可以随时传递和调用。如果你希望闭包接受参数,它们必须写在大括号内:

let sayHello = { (name: String) -> String in
    "Hi \(name)!"
}

**in**用于标记参数的结束并返回类型——之后的所有内容都是闭包本身的主体。

闭包在Swift中被广泛使用。例如,有一个名为**filter()**的数组方法,它通过测试运行数组的所有元素,任何返回测试为true的元素都会在新数组中返回。

我们可以使用闭包提供该测试,因此我们可以过滤数组,仅包含以T开头的名称:

let team = ["Gloria", "Suzanne", "Tiffany", "Tasha"]

let onlyT = team.filter({ (name: String) -> Bool in
    return name.hasPrefix("T")
})

在闭包中,我们列出了传递给我们的参数**filter()),这是数组中的字符串。我们还说,我们的闭包返回一个布尔值,然后使用in**来标记闭包代码的开头——之后,其他一切都是正常的函数代码。

尾部闭包(Trailing closures)和速记语法(shorthand syntax)

Swift有一些诀窍,使结尾更容易阅读。以下是一些过滤数组的代码,只包含以“T”开头的名称:

let team = ["Gloria", "Suzanne", "Tiffany", "Tasha"]

let onlyT = team.filter({ (name: String) -> Bool in
    return name.hasPrefix("T")
})

print(onlyT)

你可以看到,闭包的主体只有一行代码,因此我们可以去掉return:

let onlyT = team.filter({ (name: String) -> Bool in
    name.hasPrefix("T")
})

**filter()**必须给出一个从其数组中接受一个项目的函数,如果它应该在返回的数组中,则返回true。

由于我们传递的函数必须有这样的行为,所以我们不需要在闭包中指定类型。所以,我们可以重写代码成这样:

let onlyT = team.filter({ name in
    name.hasPrefix("T")
})

我们可以进一步使用称为尾随闭包语法的特殊语法,如下所示:

let onlyT = team.filter { name in
    name.hasPrefix("T")
}

最后,Swift可以为我们提供简短的参数名称,因此我们甚至不再写入**name in,而是依赖为我们提供的特殊命名值:$0**:

let onlyT = team.filter {
    $0.hasPrefix("T")
}

结构体 (Structs)

结构允许我们创建自己的自定义数据类型,并完成它们自己的属性和方法:

struct Album {
    let title: String
    let artist: String
    var isReleased = true

    func printSummary() {
        print("\(title) by \(artist)")
    }
}

let red = Album(title: "Red", artist: "Taylor Swift")
print(red.title)
red.printSummary()

当我们创建结构实例时,我们使用初始化器来创建结构——Swift允许我们像函数一样对待结构,传递其每个属性的参数。它根据结构的属性无声地生成此成员初始化器

如果你想让结构的方法更改其属性之一,请将其标记为突变

mutating func removeFromSale() {
    isReleased = false
}

计算属性 (Computed properties)

计算属性在每次访问时都会计算其值。例如,我们可以编写一个**Employee**结构,跟踪该员工的剩余假期:

struct Employee {
    let name: String
    var vacationAllocated = 14
    var vacationTaken = 0

    var vacationRemaining: Int {
        vacationAllocated - vacationTaken
    }
}

为了能够写入给**vacationRemaining**,我们需要同时提供获取器(getter)和设置器(setter):

var vacationRemaining: Int {
    get {
        vacationAllocated - vacationTaken
    }

    set {
        vacationAllocated = vacationTaken + newValue
    }
}

**newValue**由Swift提供,并存储用户分配给属性的任何值。

属性观察器 (Property observers)

属性观察器是属性更改时运行的代码片段:**didSet在属性刚刚更改时运行,willSet**在属性更改之前运行。

我们可以通过在分数更改时让**Game结构打印消息来演示didSet**:

struct Game {
    var score = 0 {
        didSet {
            print("Score is now \(score)")
        }
    }
}

var game = Game()
game.score += 10
game.score -= 3

自定义初始化器 (Custom initializers)

初始化器是一种特殊函数,用于准备要使用的新结构体实例,确保所有属性都有初始值。

Swift 会根据结构体的属性生成一个初始化器,但你也可以创建自己的初始化器:

struct Player {
    let name: String
    let number: Int

    init(name: String) {
        self.name = name
        number = Int.random(in: 1...99)
    }
}

重要:初始化程序前面没有 func,也不会明确返回值。

门禁控制(Access control)

Swift 为结构体内部的访问控制提供了多种选项,但最常见的有四种:

  • 使用 private 表示 “不要让结构体之外的任何东西使用它”。
  • 使用 private(set) 表示 “结构体之外的任何内容都可以读取此内容,但不要让它们更改”。
  • 使用 fileprivate 表示 “不允许当前文件以外的任何内容使用此文件”。
  • 使用 public 表示 “允许任何人在任何地方使用它”。

例如:

struct BankAccount {
    private(set) var funds = 0

    mutating func deposit(amount: Int) {
        funds += amount
    }

    mutating func withdraw(amount: Int) -> Bool {
        if funds > amount {
            funds -= amount
            return true
        } else {
            return false
        }
    }
}

因为我们使用了 private(set),所以从结构体外部读取资金是可以的,但写入是不可能的。

静态属性和方法(Static properties and methods)

Swift 支持静态属性和方法,允许你将属性或方法直接添加到结构体本身,而不是结构体的某个实例:

struct AppData {
    static let version = "1.3 beta 2"
    static let settings = "settings.json"
}

使用这种方法,我们可以在任何需要检查或显示应用程序版本号的地方读取 AppData.version

类(Classes)

类允许我们创建自定义数据类型,与结构体有五个不同之处。

第一个不同点是,我们可以通过继承其他类的功能来创建类:

class Employee {
    let hours: Int

    init(hours: Int) {
        self.hours = hours
    }

    func printSummary() {
        print("I work \(hours) hours a day.")
    }
}

class Developer: Employee {
    func work() {
        print("I'm coding for \(hours) hours.")
    }
}

let novall = Developer(hours: 8)
novall.work()
novall.printSummary()

如果子类要更改父类的方法,必须使用覆盖(override):

override func printSummary() {
    print("I spend \(hours) hours a day searching Stack Overflow.")
}

第二个不同点是,类的初始化器更加复杂。这里有很多复杂之处,但有三个关键点:

  1. Swift 不会为类生成成员初始化器。
  2. 如果子类有自定义初始化器,它必须在设置完自己的属性后调用父类的初始化器。
  3. 如果子类没有任何初始化器,它将自动继承父类的初始化器。

例如:

class Vehicle {
    let isElectric: Bool

    init(isElectric: Bool) {
        self.isElectric = isElectric
    }
}

class Car: Vehicle {
    let isConvertible: Bool

    init(isElectric: Bool, isConvertible: Bool) {
        self.isConvertible = isConvertible
        super.init(isElectric: isElectric)
    }
}

super 允许我们调用属于父类的方法,例如父类的初始化器。

第三个区别是,类实例的所有副本共享数据,这意味着对其中一个副本所做的更改会自动更改其他副本。

例如:

class Singer {
    var name = "Adele"
}

var singer1 = Singer()
var singer2 = singer1
singer2.name = "Justin"
print(singer1.name)  
print(singer2.name)

这将打印出两个相同的 “Justin”——尽管我们只更改了其中一个,但另一个也发生了变化。相比之下,结构体副本不会共享数据。

第四个区别是,类可以有一个去初始化器,当对象的最后一个引用被销毁时,去初始化器会被调用。

因此,我们可以创建一个类,在创建和销毁时打印一条信息:

class User {
    let id: Int

    init(id: Int) {
        self.id = id
        print("User \(id): I'm alive!")
    }

    deinit {
        print("User \(id): I'm dead!")
    }
}

for i in 1...3 {
    let user = User(id: i)
    print("User \(user.id): I'm in control!")
}

最后一个区别是,即使类本身不变,我们也可以通过类来更改变量属性:

class User {
    var name = "Paul"
}

let user = User()
user.name = "Taylor"
print(user.name)

因此,类在使用改变数据的方法时不需要使用**mutating**关键字。

协议(Protocols)

协议定义了我们期望数据类型支持的功能,Swift 确保我们的代码遵循这些规则。

例如,我们可以这样定义**Vehicle**协议:

protocol Vehicle {
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

这列出了该协议工作所需的方法,但不包含任何代码——我们只指定了方法名称、参数和返回类型。

一旦有了协议,就可以通过实现所需的功能来使数据类型符合协议。例如,我们可以创建一个符合**Vehicle**协议的 Car 结构:

struct Car: Vehicle {
    func estimateTime(for distance: Int) -> Int {
        distance / 50
    }

    func travel(distance: Int) {
        print("I'm driving \(distance)km.")
    }
}

Vehicle 中列出的所有方法必须完全存在于 Car 中,并且具有相同的名称、参数和返回类型。

现在,你可以编写一个接受任何符合 Vehicle 类型的函数,因为 Swift 知道它同时实现了 estimateTime() 和 travel()

func commute(distance: Int, using vehicle: Vehicle) {
    if vehicle.estimateTime(for: distance) > 100 {
        print("Too slow!")
    } else {
        vehicle.travel(distance: distance)
    }
}

let car = Car()
commute(distance: 100, using: car)

协议也可以要求属性,因此我们可以要求车辆有多少个座位和目前有多少乘客的属性:

protocol Vehicle {
    var name: String { get }
    var currentPassengers: Int { get set }
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

这就增加了两个属性:一个是标有 **get**的属性,可能是常量或计算属性;另一个是标有 get set 的属性,可能是变量或带有 getter 和 setter 的计算属性。

现在,所有符合要求的类型都必须添加这两个属性的实现,比如**Car**的实现:

let name = "Car"
var currentPassengers = 1

提示:你可以根据需要遵守任意多个协议,只需用逗号分隔列出即可。

扩展(Extensions)

扩展可以让我们为任何类型添加功能。例如,Swift 的字符串有一个用于修剪空白和新行的方法,但它非常长,因此我们可以将它变成一个扩展:

extension String {
    func trimmed() -> String {
        self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

var quote = "   The truth is rarely pure and never simple   "
let trimmed = quote.trimmed()

如果你想直接更改一个值,而不是返回一个新值,请像这样将方法标记为突变:

extension String {
    mutating func trim() {
        self = self.trimmed()
    }
}

quote.trim()

扩展还可以为类型添加计算属性,比如这个:

extension String {
    var lines: [String] {
        self.components(separatedBy: .newlines)
    }
}

components(separatedBy:) 方法使用我们选择的边界(在本例中是新行)将字符串分割成字符串数组。

现在,我们可以在所有字符串中使用该属性:

let lyrics = """
But I keep cruising
Can't stop, won't stop moving
"""

print(lyrics.lines.count)

协议扩展(Protocol extensions)

协议扩展扩展了整个协议,添加了计算属性和方法实现,因此任何符合该协议的类型都能获得它们。

例如,数组(Array)、字典(Dictionary)和集合(Set)都符合**Collection**协议,因此我们可以像这样为它们添加计算属性:

extension Collection {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

现在我们可以派上用场了:

let guests = ["Mario", "Luigi", "Peach"]

if guests.isNotEmpty {
    print("Guest count: \(guests.count)")
}

这种方法意味着我们可以在协议中列出所需的方法,然后在协议扩展中添加这些方法的默认实现。然后,所有符合要求的类型都可以使用这些默认实现,或根据需要提供自己的实现。

可选项(Optionals)

可选项表示没有数据,例如,可选项可以区分数值为 0 的整数和没有数值的整数。

要了解可选项的作用,请看这段代码:

let opposites = [
    "Mario": "Wario",
    "Luigi": "Waluigi"
]

let peachOpposite = opposites["Peach"]

它试图读取连接到键 “Peach”的值,而这个键并不存在,所以它不可能是一个普通字符串。Swift 的解决方案被称为可选项,即可能存在也可能不存在的数据。

一个可选字符串可能有一个字符串在里面等着我们,也可能什么都没有——一个叫做 nil 的特殊值,意思是 “无值”。任何类型的数据都可以是可选的,包括 Int、Double 和 Bool,以及枚举、结构体和类的实例。

Swift 不会让我们直接使用可选数据,因为它可能是空的。这意味着我们需要解包可选数据才能使用它——我们需要查看其内部是否存在值,如果存在,则取出并使用它。

Swift 提供了几种解包可选项的方法,但你最常见的方法是这样的:

if let marioOpposite = opposites["Mario"] {
    print("Mario's opposite is \(marioOpposite)")
}

这将从字典中读取可选值,如果可选值内有字符串,则将其解包——字符串将被放入 marioOpposite 常量中,不再是可选值。由于我们已经解开了可选值,因此条件成功,**print()**代码也就运行了。

用 guard 解包可选项

Swift 还有第二种解包可选项的方法,称为 guard let,它与 if let 非常相似,只是相反:如果可选项有值,if let 会运行括号内的代码;如果可选项没有值,guard let 会运行代码。

它看起来像这样:

func printSquare(of number: Int?) {
    guard let number = number else {
        print("Missing input")
        return
    }

    print("\(number) x \(number) is \(number * number)")
}

如果使用 guard 检查函数的输入是否有效,Swift 要求在检查失败时使用 return。但是,如果你正在解包的可选项内部有一个值,你可以在 guard 代码结束后使用它。

提示:你可以在任何条件下使用 guard,包括不解除可选项的条件。

空聚合(Nil coalescing)

Swift 还有第三种解包可选项的方法,称为空聚合操作符(nil coalescing operator),它可以解包可选项,并在可选项为空时提供默认值:

let tvShows = ["Archer", "Babylon 5", "Ted Lasso"]
let favorite = tvShows.randomElement() ?? "None"

在创建可选项的许多地方,空聚合操作符都很有用。例如,从字符串创建整数时会返回一个可选的 Int? 在这里,我们可以使用空聚合提供一个默认值:

let input = ""
let number = Int(input) ?? 0
print(number)

可选项链(Optional chaining)

可选项链读取可选项内的可选项,就像这样:

let names = ["Arya", "Bran", "Robb", "Sansa"]
let chosen = names.randomElement()?.uppercased()
print("Next in line: \(chosen ?? "No one")")

可选项链在第 2 行:一个问号,后面跟着更多代码。它允许我们说:“如果可选项里面有一个值,那么就把它拆开…… ”并添加更多代码。在我们的例子中,我们说的是 “如果我们从数组中得到了一个随机元素,就把它大写”。

可选项try?(Optional try?)

当调用一个可能会出错的函数时,我们可以使用 try? 将其结果转换为一个可选项,在成功时包含一个值,反之则为 nil

如下所示:

enum UserError: Error {
    case badID, networkFailed
}

func getUser(id: Int) throws -> String {
    throw UserError.networkFailed
}

if let user = try? getUser(id: 23) {
    print("User: \(user)")
}

getUser() 函数总是会抛出 networkFailed,但我们并不关心抛出的是什么,我们只关心调用是否发回了用户。

总结

至此,我们一起学习了 Swift 语言的大部分基础知识,但实际上我们只是触及了这门语言的皮毛。幸运的是,我们所学到的知识已经足以让你使用 Swift 和 SwiftUI 构建一些出色的软件。

接下来,我们将开始学习SwiftUI来创建一些简单的App,如果你有兴趣请继续关注我的教程。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注