经过前面大约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]
每一个都包含不同类型的数据:一个字符串、一个整数和一个小数。当我们从数组中读取数据时,我们将得到适当的类型-String
、Int
或Double
:
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)
使用**if
、else
和else 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.")
}
第二个不同点是,类的初始化器更加复杂。这里有很多复杂之处,但有三个关键点:
- Swift 不会为类生成成员初始化器。
- 如果子类有自定义初始化器,它必须在设置完自己的属性后调用父类的初始化器。
- 如果子类没有任何初始化器,它将自动继承父类的初始化器。
例如:
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,如果你有兴趣请继续关注我的教程。