可选链 、错误处理、类型转换、嵌套类型

可选链(Optional Chaining)式调用是一种可以在当前值为nil的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么会调用成功;如果可选值是nil,那么调用将返回nil

使用可选链式调用代替强制展开

通过调用的属性、方法或下标的可选值后面放一个问号 ‘?‘,可以定义一个可选链。这一点很像在可选值后面放一个叹号’!‘来强制展开它的值。它们主要的区别在于可选值为空时可选链式调用只会调用失败,然后强制展开会将触发运行时错误。

可选链式调用定义模型

通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标

通过可选链式调用访问属性
1
2
3
4
5
6
7
//这里没有去创建相关的类。
let john = Person()
if let roomCount = john.residence?.numberOfRooms {

}else {

}
通过可选链式调用方法
1
2
3
4
5
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms")
}else {
print("It was not possible to print teh number of rooms")
}
通过可选链式调用访问下标

通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。

1
2
3
4
5
if let firstRoomName = john.residence?[0].name {

}
//用可选链调用来赋值
john.residence?[0] = Room(name: "Bathroom")
链接多层可选链式调用

可选通过连接多个可选链式调用在更深的模型层次中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回可选值的可选层次。

通过可可选链式访问一个Int值,将返回Int?,无论使用了多层可选链式调用。

1
2
3
if let johnsStreet = john. john.residence?.address?.street {

}

错误处理

错误处理是响应错误以及从错误中恢复的过程。

表示并抛出错误

在Swift中,错误用符合Error协议的类型的值来表示。这个空协议表明该类型可以用于错误处理

Swift枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。

1
2
3
4
5
enum VendingMachineError : Error {
case invalidSelection //选择无效
case insufficientFunds( coinsNeeded: Int) //金额不足
case outOfStock //缺货
}

抛出错误使用 throw关键字。

1
throw  VendingMachineError.insufficientFunds(coinsNeeded: 5)

处理错误

Swift中有4种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用 do-catch 语句处理错误、 将错误作为可选类型、或者断言此错误根本就不会发生。

在调用一个能抛出错误的函数、方法或者构造器之前,加上 try 关键字,或者 try? 或 try! 这种变体。

用 throwing函数 传递错误

为了标识一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 throws 关键字。一个标有 throws关键字的函数称做 throwing函数。

1
func canThrowErrors() throws -> String

一个throwing函数可以在其内部抛出错误,并将错误传递到函数被调用的作用域。

注意:只有throwing 函数可以传递错误。任何在某个非throwing函数内部抛出的错误只能在函数内部处理

1
2
3
4
5
6

func vend(name: String) throws {
guard name.isEmpty else {
throw VendingMachineError.invalidSelection
}
}

因为vend(name:)方法会传递出它抛出的任何错误。在你的代码中调用此方法要么直接处理这些错误(使用do-catch,try?或try!);要么继续将错误传递下去。

1
2
3
4
func buyFavouriteSnack(person: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(name: snackName)
}
//将错误继续传递下去
用Do-Catch处理错误

如果在do子语句中的代码抛出了一个错误,这个错误会与catch子语句做匹配。从而决定哪条子句能处理它

1
2
3
4
5
6
7
8
9
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
如果do代码块有错误抛出,相应的执行会马上转移到catch子句中,并判断这个错误是否要继续传递下去。如果没有错误抛出,do子句中余下的语句会被执行
将错误转换成可选值

可以使用try?通过错误转换成一个可选值来处理。

1
2
3
4
5
6
7
8
9
10
11
func someThrowingFunction() throws -> Int {
//...
}
let x = try? someThrowingFunction()

let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}

如果someThrowingFunction() 抛出错误,x和y的值都是nil。否则x和y的值是函数的返回值。无论someThrowingFunction()的返回值是什么类型,x,y都是这个类型的可选类型。

禁用错误传递

有时你知道某个throwing函数实际上运行时是不会抛出错误的,在这种情况下,你可以再表达式前面写try!来禁止用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果有错误抛出,你会得到一个运行时错误

1
let phone = try! loadImage(atPath: "./Resources/John")
指定清理操作

你可以使用 defer语句在即将离开当前代码块是执行一系列语句。该语句能执行一些必要清理工作。

defer语句块将代码的执行延迟到当前作用域退出之前。延迟执行的语句不能包含任何控制转换语句,例如break、return语句或是抛出一个错误。

1
2
3
4
5
6
7
8
9
10
11
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try fiel.readline() {
// 处理文件
}
}
}

类型转换

类型转换可以判定实例的类型,也可以将实例看做是其父类或者子类的实例。

类型转换在Swift中使用isas操作符实现。你可以用这2个操作符去检查值的类型和转换它的类型,也可以用它来检查是否实现了某个协议

为类型转换定义类的层次
1
2
3
4
5
6
7
8
9
10
11
class MediaItem {}

class Movie: MediaItem {}

class Song: MediaItem {}

let library = [
Movie()
Song()
]
//数组 library 的类型被推断为[MediaItem]
检查类型

is来检查实例是否属于某个特定的子类。

1
2
3
4
5
6
for item in  library {
if item is Movie {
// is Movie
}else if item is Song {
}
}
向下转型

某类型的一个常量或变量可能在幕后实际上是属于一个子类。当确定这种情况时,你可以尝试向下转到它的子类型。用 as?as!

向下转型可能会失败。as?返回一个你试图向下转成的类型的可选类型。as!是试图向下转型和强制解包转换结果结为一体。如果失败会触发运行时错误

1
2
3
4
5
6
for item in library {
if let movie = item as? Movie {
//is Movie
}else if let song = item as? Song {
}
}

Any 和 AnyObject

Swift 中提供了2中特殊的类型别名:

  • Any 可以表示任何类型,包括函数类型
  • AnyObject 可以表示任何类类型的实例
1
2
3
4
5
6
7
8
var things = [Any]()
things.append(0)
things.append(0.0)
things.append("hello")

let optionalInt : Int? = 3
things.append(optionalInt) //有警告
things.append(optionalInt as Any) //没有警告

注意:
Any类型可以表示所有类型的值,包括可选类型。Swift会在你用Any类型来表示一个可选类型的时候,会给你一个警告。如果你确实想使用Any类型来承载一个可选值,你可以使用as 操作符显示转换为Any

嵌套类型

枚举被常用于为特定的类或结果实现某些功能。类似的,枚举可以方便的定义工具类或结构体。Swift 允许定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体

嵌套类型实践
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct BlackjackCard {
//嵌套的 Suit 枚举
enum Suit: Character {
case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
}

//嵌套的Rank类型
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack , queen, king, ace

struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .ace:
return Values(first: 1 ,second: 11)
case: .jack, .queen, .king
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}

}
}
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.rawValue),"
output += "values is \(rank.values.first)"
if let second = rank.values.second {
output += "or\(second)"
}
return output
}
}

let theAceOfSpades = BlackjackCard(rank: .ace, suit:.spades);
print("\(theAceOfSpades.description)")
0%