主要内容:
一.Swift 2.0之前的错误处理
错误处理是应用中响应错误与从错误中恢复的过程。在Swift 2.0之前,错误报告与Objective-C的模式相同,但是Swift可以使用可选类型的返回值,返回nil
表示函数内部发生了错误。
最简单的错误处理,函数的返回值会表明函数执行是否成功:返回值可以是布尔值true/false
,或者枚举enum
,如果函数没有成功执行的话,该值可以表明哪里发生了错误。如果需要报告关于错误的额外信息,我们可以添加NSErrorPointer
类型的NSError
参数,但这不是最简单的途径,这些错误往往会被开发者忽略。下面的例子展示了Swift 2.0之前如何处理错误的:
var str = "Hello World"
var error: NSError
var results = str.writeToFile(path,atomically: true,encoding:NSUTF8StringEncoding,error: &error)
if results {
//
} else {
println("Error writing filer: \(error)")
}
尽管可以以这种方式处理错误,能够调整来满足大多数的需求,但是它一定不是最好的方案。这种方案有一些问题,最大的问题是开发者容易忽略函数返回值和错误自身。大多数有经验的开发者对于检查错误很小心,但是初学者很难理解,特别是函数没有包含NSError
参数时。
除了使用NSError
,我们也可以使用NSException
类来抛出与捕获异常。但是,很少的开发者使用这种方法,甚至在Cocoa与Cocoa Touch框架中,这种处理异常的方法也很少被使用。
尽管使用NSError
类与返回值来处理错误奏效,但是很多人,对于Apple刚开始发布Swift时没有包含额外的错误处理很失望。现在,Swift 2.0,我们拥有原生的错误处理。
二.Swift 2中的错误处理
《一.表示错误
在理解Swift中错误处理的工作原理前,必须要明白如何表示错误。在Swift中,错误是由遵守ErrorType
协议的类型的值表示的。枚举非常适合作为错误情况的模型,因为通常我们有有限数量的错误情况来表示。
我们来看一下如何使用枚举来表示错误,首先,定义一个名字为MyError
的错误,该错误有三种错误情况:Minor
、Bad
、Terrible
。
enum MyError: ErrorType {
case Minor
case Bad
case Terrible
}
在这个例子中,定义了遵守ErrorType
协议的MyError
枚举,然后定义了三中错误情况。我们也可以对错误情况使用相关值associated values
。为其中一个错误情况添加描述:
enum MyError: ErrorType {
case Minor
case Bad
case Terrible (description: String)
}
三.捕获错误
当函数抛出错误时,我们需要在调用该函数的代码中捕获它。使用do-catch
代码块来完成,语法如下:
do {
try 调用会抛出错误的函数
...
} catch [pattern] {
...
}
如果错误被抛出,它会一直往外传递,直到被catch
处理。其中,catch
后面跟上匹配错误的模式(a pattern to match the error
)
import Cocoa
let maxNumber = 100
let minNumber = 8
enum PlayerNumberError: ErrorType {
case NumberTooHigh(description: String)
case NumberTooLow(description: String)
case NumberAlreadyAssigned
case NumberDoesNotExist
}
typealias BaseballPlayer = (firstName: String,lastName: String,number: Int)
struct BaseballTeam {
var players: [Int: BaseballPlayer] = [ : ]
mutating func addPlayer(player: BaseballPlayer) throws {
guard player.number < maxNumber else {
throw PlayerNumberError.NumberTooHigh(description: "Max number is \(maxNumber)")
}
guard player.number > minNumber else {
throw PlayerNumberError.NumberTooLow(description: "Min number is \(minNumber)")
}
guard players[player.number] == nil else {
throw PlayerNumberError.NumberAlreadyAssigned
}
players[player.number] = player
}
func getPlayerByNumber(number: Int) throws -> BaseballPlayer {
if let player = players[number] {
return player
} else {
throw PlayerNumberError.NumberDoesNotExist
}
}
}
var myTeam = BaseballTeam(players: [ : ])
do {
let player = try myTeam.getPlayerByNumber(8)
print("Player is \(player.firstName) \(player.lastName)")
} catch PlayerNumberError.NumberDoesNotExist {
print("No player has that number")
}
上述例子中,do-catch
块中调用了getPlayerByNumber()
方法,如果队中没有成员分配该数字,则该方法会抛出PlayerNumberError.NumberDoesNotExist
错误情况。因此,我们在catch
表达式中匹配那个错误。
在catch
后面不必包含一个模式,如果catch
后面没有包含一个模式,或者放置一个下划线,catch
表达式会匹配所有的错误情况。例如,下面任何一个catch
表达式都会捕获所有的错误:
do { } catch { }
do { } catch _ { }
do {
} catch let error {
}
使用catch
表达式来捕获不同的错误情况:
do {
try myTeam.addPlayer(("David","Ortiz",10))
} catch PlayerNumberError.NumberTooHigh(let description) {
print("Error: \(description)")
} catch PlayerNumberError.NumberTooLow(let description) {
print("Error: \(description)")
} catch PlayerNumberError.NumberAlreadyAssigned {
print("Error: Number already assigned")
}
我们也可以让错误传递出去,而不是立即捕获它们。为了实现这样的目的,只需要在函数定义中添加throws
关键字。例如下面的例子中,将错误传递到调用该函数的代码中,而不是在函数中处理错误。
func myFunc() throws {
try myTeam.addPlayer(("David",34))
}
如果我们确定错误不会被抛出,可以使用强制try(forced-try
)表达式来调用函数,写法是try!
。强制try表达式阻止错误传递,会将函数调用包装在运行时断言中,这样调用就不会抛出错误。如果抛出了错误,就会得到运行时错误,所以使用这种表达式时要注意。
try?
关键字尝试执行可能会抛出错误的操作。如果操作成功,会返回可选类型的结果。但是如果操作失败,抛出错误,会返回nil,错误会被丢弃。
因为try?
关键字返回的结果是可选类型,通常将其与可选类型绑定结合,如下:
if let player = try? myTeam.getPlayerByNumber(34) {
print("Player is \(player.firstName)")
}
如果需要执行一些清理工作,而不管是否有一些错误,可以使用defer
。使用defer
来在代码执行刚离开当前作用域前执行代码块,如下:
func deferFunction() {
print("Function started")
var str: String?
defer {
print("In defer block")
if let s = str {
print("str is \(s)")
}
}
str = "John"
print("Function finished")
}
如果调用这个函数,打印到控制台的第一行是Function started
,代码的执行会跳过defer
块,接着Function finished
会打印到控制台。最后,离开函数域前,defer
块会执行。执行的结果如下:
Function started
Function finished
In defer block
str is John
availability
属性
Swift使用availability
属性来安全地包装代码,只有在正确版本的操作系统可获得时,该代码才会运行。有两种方式来使用availability
属性,第一种方式允许我们执行特定的代码块,与if
或guard
一同工作。第二种方式允许我们将一个方法或类型标记为只在特定平台上可获得。
availability
属性可接受五个用逗号隔开的参数,允许我们定义执行代码所需要的操作系统与应用扩展的最低版本。这些参数包括:
iOS
:兼容代码的最低iOS版本OSX
:兼容代码的最低OS X版本watchOS
:兼容代码的最低watchOS版本iOSApplicationExtension
:兼容代码的最低iOS应用kuozOSXApplicationExtension
:兼容代码的最低OS X应用扩展
使用*
来结束参数列表,我们来看一下如何只有在满足最小要求时才执行特定的代码块:
if #available(iOS 9.0,OSX 10.10,watchOS 2,*) {
print("Minimum requirements met")
} else {
print("Minimum requirements not met")
}
我们也可以限制对函数或类型的获取,之前availability
的前缀是#
,为了限制对函数或类型的获取,使用@
作为前缀。
@available(iOS 9.0,*) func testAvailability() {
}
@available(iOS 9.0,*) struct TestStruct {
}
为了使用@available
属性来限制对函数与类型的获取,必须要使用#available
将调用该函数与类型的代码进行包装。
if #available(iOS 9.0,*) {
testAvailability()
} else {
}