錯誤處理
錯誤型別
開發程序中常見的錯誤有
- 語法錯誤(編譯報錯)
- 邏輯錯誤
- 運行時錯誤(可能會導致閃退,一般也叫做例外)
- ....
自定義錯誤
Swift中可以通過Error協議自定義運行時的錯誤資訊
enum SomeError: Error {
case illegalArg(String)
case outOffBounds(Int, Int)
case outOfMemory
}
函式內部通過throw拋出自定義Error,可能會拋出Error的函式必須加上throws宣告
func divide(_ num1: Int, _ num2: Int) throws -> Int {
if num2 == 0 {
throw SomeError.illegalArg("0不能作為除數")
}
return num1 / num2
}
需要使用try呼叫可能會拋出Error的函式
var result = try divide(20, 10)
拋出錯誤資訊的情況

do—catch
可以使用do—catch捕捉Error
do {
try divide(20, 0)
} catch let error {
switch error {
case let SomeError.illegalArg(msg):
print("引數錯誤", msg)
default:
print("其他錯誤")
}
}
拋出Error后,try下一句直到作用域結束的代碼都將停止運行
func test() {
print("1")
do {
print("2")
print(try divide(20, 0)) // 這句拋出例外后面的代碼不會執行了
print("3")
} catch let SomeError.illegalArg(msg) {
print("引數例外:", msg)
} catch let SomeError.outOffBounds(size, index) {
print("下標越界:", "size=\(size)", "index=\(index)")
} catch SomeError.outOfMemory {
print("記憶體溢位")
} catch {
print("其他錯誤")
}
print("4")
}
test()
//1
//2
//引數例外: 0不能作為除數
//4
catch作用域內默認就有error的變數可以捕獲
do {
try divide(20, 0)
} catch {
print(error)
}
處理Error
處理Error的兩種方式
通過do—catch捕捉Error
do {
print(try divide(20, 0))
} catch is SomeError {
print("SomeError")
}
不捕捉Error,在當前函式增加throws宣告,Error將自動拋給上層函式
如果最頂層函式main函式依然沒有捕捉Error,那么程式將終止
func test() throws {
print("1")
print(try divide(20, 0))
print("2")
}
try test()
// 1
// Fatal error: Error raised at top level
呼叫函式如果是寫在函式里面,沒有進行捕捉Error就會報錯,而寫在外面就不會

然后我們加上do-catch發現還是會報錯,因為捕捉Error的處理不夠詳細,要捕捉所有Error資訊才可以

這時我們加上throws就可以了
func test() throws {
print("1")
do {
print("2")
print(try divide(20, 0))
print("3")
} catch let error as SomeError {
print(error)
}
print("4")
}
try test()
// 1
// 2
// illegalArg("0不能作為除數")
// 4
或者再加上一個catch捕獲其他所有Error情況
func test() {
print("1")
do {
print("2")
print(try divide(20, 0))
print("3")
} catch let error as SomeError {
print(error)
} catch {
print("其他錯誤情況")
}
print("4")
}
test()
看下面示例代碼,執行后會輸出什么
func test0() throws {
print("1")
try test1()
print("2")
}
func test1() throws {
print("3")
try test2()
print("4")
}
func test2() throws {
print("5")
try test3()
print("6")
}
func test3() throws {
print("7")
try divide(20, 0)
print("8")
}
try test0()
執行后列印如下,并會拋出錯誤資訊

try
可以使用try?、try!呼叫可能會拋出Error的函式,這樣就不用去處理Error
func test() {
print("1")
var result1 = try? divide(20, 10) // Optional(2), Int?
var result2 = try? divide(20, 0) // nil
var result3 = try! divide(20, 10) // 2, Int
print("2")
}
test()
a、b是等價的
var a = try? divide(20, 0)
var b: Int?
do {
b = try divide(20, 0)
} catch { b = nil }
rethrows
rethrows表明,函式本身不會拋出錯誤,但呼叫閉包引數拋出錯誤,那么它會將錯誤向上拋
func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
print(try fn(num1, num2))
}
// Fatal error: Error raised at top level
try exec(divide, 20, 0)
空合并運算子就是用了rethrows來進行宣告的

defer
defer陳述句,用來定義以任何方式(拋錯誤、return等)離開代碼塊前必須要執行的代碼
defer陳述句將延遲至當前作用域結束之前執行
func open(_ filename: String) -> Int {
print("open")
return 0
}
func close(_ file: Int) {
print("close")
}
func processFile(_ filename: String) throws {
let file = open(filename)
defer {
close(file)
}
// 使用file
// .....
try divide(20, 0)
// close將會在這里呼叫
}
try processFile("test.txt")
// open
// close
// Fatal error: Error raised at top level
defer陳述句的執行順序與定義順序相反
func fn1() { print("fn1") }
func fn2() { print("fn2") }
func test() {
defer { fn1() }
defer { fn2() }
}
test()
// fn2
// fn1
assert(斷言)
很多編程語言都有斷言機制,不符合指定條件就拋出運行時錯誤,常用于除錯Debug階段的條件判斷

默認情況下,Swift的斷言只會在Debug模式下生效,Release模式下會忽略
增加Swift Flags修改斷言的默認行為
-assert-config Release:強制關閉斷言-assert-config Debug:強制開啟斷言

fatalError
如果遇到嚴重問題,希望結束程式運行時,可以直接使用fatalError函式拋出錯誤
這是無法通過do—catch捕獲的錯誤
使用了fatalError函式,就不需要再寫return
func test(_ num: Int) -> Int {
if num >= 0 {
return 1
}
fatalError("num不能小于0")
}
在某些不得不實作,但不希望別人呼叫的方法,可以考慮內部使用fatalError函式
class Person { required init() {} }
class Student: Person {
required init() {
fatalError("don't call Student.init")
}
init(score: Int) {
}
}
var stu1 = Student(score: 98)
var stu2 = Student()
區域作用域
可以使用do實作區域作用域
do {
let dog1 = Dog()
dog1.age = 10
dog1.run()
}
do {
let dog2 = Dog()
dog2.age = 10
dog2.run()
}
泛型(Generics)
基本概念
泛型可以將型別引數化,提高代碼復用率,減少代碼量
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var i1 = 10
var i2 = 20
swap(&i1, &i2)
print(i1, i2) // 20, 10
struct Date {
var year = 0, month = 0, day = 0
}
var d1 = Date(year: 2011, month: 9, day: 10)
var d2 = Date(year: 2012, month: 10, day: 20)
swap(&d1, &d2)
print(d1, d2) // Date(year: 2012, month: 10, day: 20), Date(year: 2011, month: 9, day: 10)
泛型函式賦值給變數
func test<T1, T2>(_ t1: T1, _ t2: T2) {}
var fn: (Int, Double) -> () = test
泛型型別
class Stack<E> {
var elements = [E]()
func push(_ element: E) {
elements.append(element)
}
func pop() -> E {
elements.removeLast()
}
func top() -> E {
elements.last!
}
func size() -> Int {
elements.count
}
}
class SubStack<E>: Stack<E> {
}
var intStack = Stack<Int>()
var stringStack = Stack<String>()
var anyStack = Stack<Any>()
struct Stack<E> {
var elements = [E]()
mutating func push(_ element: E) {
elements.append(element)
}
mutating func pop() -> E {
elements.removeLast()
}
func top() -> E {
elements.last!
}
func size() -> Int {
elements.count
}
}
var stack = Stack<Int>()
stack.push(11)
stack.push(22)
stack.push(33)
print(stack.top()) // 33
print(stack.pop()) // 33
print(stack.pop()) // 22
print(stack.pop()) // 11
print(stack.size()) // 0
enum Score<T> {
case point(T)
case grade(String)
}
let score0 = Score<Int>.point(100)
let score1 = Score.point(99)
let score2 = Score.point(99.5)
let score3 = Score<Int>.grade("A")
關聯型別(Associated Type)
關聯型別的作用:給協議中用到的型別定義一個占位名稱
protocol Stackable {
associatedtype Element
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
struct Stack<E>: Stackable {
var elements = [E]()
mutating func push(_ element: E) {
elements.append(element)
}
mutating func pop() -> E {
elements.removeLast()
}
func top() -> E {
elements.last!
}
func size() -> Int {
elements.count
}
}
class StringStack: Stackable {
var elements = [String]()
func push(_ element: String) {
elements.append(element)
}
func pop() -> String {
elements.removeLast()
}
func top() -> String {
elements.last!
}
func size() -> Int {
elements.count
}
}
var ss = StringStack()
ss.push("Jack")
ss.push("Rose")
協議中可以擁有多個關聯型別
protocol Stackable {
associatedtype Element
associatedtype Element2
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
型別約束
protocol Runnable { }
class Person { }
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
protocol Stackable {
associatedtype Element: Equatable
}
class Stack<E: Equatable>: Stackable {
typealias Element = E
}
func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
return false
}
var stack1 = Stack<Int>()
var stack2 = Stack<String>()
equal(stack1, stack2)
協議型別的注意點
看下面的示例代碼來分析
protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
如果協議中有associatedtype
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person: Runnable {
var speed: Double { 0.0 }
}
class Car: Runnable {
var speed: Int { 0 }
}
這樣寫會報錯,因為無法在編譯階段知道Speed的真實型別是什么

可以用泛型來解決
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person: Runnable {
var speed: Double { 0.0 }
}
class Car: Runnable {
var speed: Int { 0 }
}
func get<T: Runnable>(_ type: Int) -> T {
if type == 0 {
return Person() as! T
}
return Car() as! T
}
var r1: Person = get(0)
var r2: Car = get(1)
還可以使用some關鍵字宣告一個不透明型別
some限制只能回傳一種型別
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}
class Person: Runnable {
var speed: Double { 0.0 }
}
class Car: Runnable {
var speed: Int { 0 }
}
func get(_ type: Int) -> some Runnable {
return Car()
}
var r1 = get(0)
var r2 = get(1)
some除了用在回傳值型別上,一般還可以用在屬性型別上
protocol Runnable {
associatedtype Speed
}
class Dog: Runnable {
typealias Speed = Double
}
class Person {
var pet: some Runnable {
return Dog()
}
}
泛型的本質
我們通過下面的示例代碼來分析其內部具體是怎樣實作的
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var i1 = 10
var i2 = 20
swap(&i1, &i2)
print(i1, i2) // 20, 10
var d1 = 10.0
var d2 = 20.0
swap(&d1, &d2)
print(d1, d2) // 20.0, 10.0
反匯編之后


從呼叫兩個交換方法來看,最終呼叫的都是同一個函式,因為函式地址是一樣的;但不同的是分別會將Int的metadata和Double的metadata作為引數傳遞進去,所以推測metadata中應該分別指明對應的型別來做處理
可選項的本質
可選項的本質是enum型別
我們可以進到頭檔案中查看

我們平時寫的語法糖的真正寫法如下
var age: Int? = 10
本質寫法如下:
var ageOpt0: Optional<Int> = Optional<Int>.some(10)
var ageOpt1: Optional = .some(10)
var ageOpt2 = Optional.some(10)
var ageOpt3 = Optional(10)
var age: Int? = nil
本質寫法如下:
var ageOpt0: Optional<Int> = .none
var ageOpt1 = Optional<Int>.none
var age: Int? = .none
age = 10
age = .some(20)
age = nil
switch中可選項的使用
switch age {
case let v?: // 加上?表示如果有值會解包賦值給v
print("some", v)
case nil:
print("none")
}
switch age {
case let .some(v):
print("some", v)
case .none:
print("none")
}
多重可選項
var age_: Int? = 10
var age: Int?? = age_
age = nil
var age0 = Optional.some(Optional.some(10))
age0 = .none
var age1: Optional<Optional> = .some(.some(10))
age1 = .none
var age: Int?? = 10
var age0: Optional<Optional> = 10
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/275006.html
標籤:iOS
