一、錯誤處理
-
1.1、錯誤類型
- 語法錯誤(編譯報錯)
- 邏輯錯誤
- 運行時錯誤(可能會導致閃退,一般也叫做異常)
-
1.2、自定義錯誤
-
Swift中可以通過
Error
協議自定義運行時的錯誤信息enum SomeError : Error { case illegalArg(String) case outOfBounds(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
的函數do { let result = try divide(10, 0) print("result=\(result)") } catch { print(error) }
-
-
1.3、do-catch 捕獲錯誤
可以使用do-catch捕捉Errorfunc test() { print("1") do { print("2") print(try divide(20, 0)) print("3") } catch let SomeError.illegalArg(msg) { print("參數異常:", msg) } catch let SomeError.outOfBounds(size, index) { print("下標越界:", "size=\(size)", "index=\(index)") } catch SomeError.outOfMemory { print("內存溢出") } catch { print("其他錯誤") } print("4") }
-
拋出Error后,try下一句直到作用域結束的代碼都將停止運行
test() // 1 // 2 // 參數異常: 0不能作為除數 // 4 do { try divide(20, 0) } catch let error { switch error { case let SomeError.illegalArg(msg): print("參數錯誤:", msg) default: print("其他錯誤") } }
-
-
1.4、處理Error
處理Error的2種方式-
通過do-catch捕捉Error
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
-
不捕捉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 do { print(try divide(20, 0)) } catch is SomeError { print("SomeError") }
提示:
try
僅僅代表嘗試去調用一個函數
-
-
1.5、try?、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 }
提示:
b = try divide(20, 0)
在 try 后面拋出錯區后,就不會再給 b 賦值,直接走catch
-
-
1.6、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 是一個聲明,上述的 rethrows 代表的是上面拋出的 錯誤不是
exec 函數
本身,而是 函數內部的fn 函數
拋出的錯誤- 總之:
rethrows
還是throws
區別 如下 - 共同點:都代表往外拋出錯誤
- 不同點:
throws
代表不是通過傳遞進來的參數調用導致的異常,是函數內部寫其他代碼導致的異常;rethrows
:是因為傳進來的參數調用導致的異常
- 總之:
-
1.7、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
總結:defer
- 語句的
執行順序
與定義順序
相反 - 當前作用域結束之前執行
- 語句的
-
1.8、assert (斷言)
很多編程語言都有斷言機制:不符合指定條件就拋出運行時錯誤,常用于調試(Debug)階段的條件判斷
-
默認情況下,Swift的斷言只會在Debug模式下生效,Release模式下會忽略
func divide(_ v1: Int, _ v2: Int) -> Int { assert(v2 != 0, "除數不能為0") return v1 / v2 } print(divide(20, 0))
-
增加Swift Flags修改斷言的默認行為
-assert-config Release
:強制關閉斷言
-assert-config Debug
:強制開啟斷言增加Swift Flags修改斷言的默認行為
-
1.9、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()
-
1.10、局部作用:可以使用
do
實現局部作用域do { let dog1 = Dog() dog1.age = 10 dog1.run() } do { let dog2 = Dog() dog2.age = 10 dog2.run() }
二、泛型 (Generics)
-
2.1、泛型可以將類型參數化,提高代碼復用率,減少代碼量
func swapValues<T>(_ a: inout T, _ b: inout T) { (a, b) = (b, a) } var x1 = 10 var x2 = 20 swapValues(&x1, &x2) var d1 = 10.0 var d2 = 20.0 swapValues(&d1, &d2) struct Date { var year = 0, month = 0, day = 0 } var dd1 = Date(year: 2011, month: 9, day: 10) var dd2 = Date(year: 2012, month: 10, day: 11) swapValues(&dd1, &dd2)
提示 :T 僅僅是參數的泛型名字,泛型名字定義要有意義,可以根據自己的需要定義為其他的名字
-
2.2、泛型函數賦值給變量, T1 和 T2 僅僅代表函數 的兩個參數的類型不同
func test<T1, T2>(_ t1: T1, _ t2: T2) {} var fn1: (Int, Double) -> () = test var fn2: (String, Double) -> () = test
-
2.3、泛型使用例子
// 父類 class Stack<E> { var elements = [E]() init(firstElement: E){ elements.append(firstElement) } 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 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 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 } }
提示:在結構體的泛型里面,在函數里面修改變量,要加上 mutating
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")
-
2.4、關聯類型
關聯類型的作用:給協議中用到的類型定義一個占位名稱
-
協議中可以擁有多個關聯類型
protocol Stackable { // 協議中可以擁有多個關聯類型 associatedtype Element // 關聯類型 1 associatedtype Element2 // 關聯類型 2 mutating func push(_ element: Element) mutating func pop() -> Element func top() -> Element func size() -> Int }
提示:
associatedtype Element
關聯類型 這是 swift的規定,而不能像函數、類、結構體 - ><泛型名字>
明確關聯的類型,
typealias Element = String
,也可以省略,因為func push(_ element: String)
函數可以識別類型class StringStack : Stackable { // 給關聯類型設定真實類型 // typealias Element = String 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")
類設置泛型,可以如下定義,同時
typealias Element = E
可以省略class Stack<E> : Stackable { // typealias Element = 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 } }
-
2.5、對泛型進行約束
-
通過 繼承基類 或者 遵守協議 和 通過
where
語句來 進行約束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>() // error: requires the types 'Int' and 'String' be equivalent equal(stack1, stack2)
提示:傳進來的參數泛型要是 Person 類型并且遵守Runnable協議,還有 where 判斷
-
-
2.6、協議類型的注意點
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) -> Runnable { if type == 0 { return Person() } return Car() } var r1 = get(0) var r2 = get(1)
提示: func get(_ type: Int) -> Runnable 會報錯:
Protocol 'Runnable8' can only be used as a generic constraint because it has Self or associated type requirements
原因:Runnable 協議里面
associatedtype
關聯類型-
解決辦法1:使用泛型
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)
-
解決辦法2:使用
some
關鍵字聲明一個不透明類型func get(_ type: Int) -> some Runnable { return Car() } var r1 = get(0) var r2 = get(1)
-
2.7、不透明類型
-
some 限制只能返回一種類型
func get(_ type: Int) -> some Runnable { if type == 0 { return Person() } return Car() }
提示:報錯 Function declares an opaque return type, but the return statements in its body do not have matching underlying types
-
some 除了用在返回值類型上,一般還可以用在屬性類型上
protocol Runnable { associatedtype Speed } class Dog : Runnable { typealias Speed = Double } class Person { var pet: some Runnable { return Dog() } } var person = Person10() person.pet
提示:我們可以看到返回的是遵守了 Runnable 協議的對象
-
-
2.8、可選項的本質是
enum
類型public enum Optional<Wrapped> : ExpressibleByNilLiteral { case none case some(Wrapped) public init(_ some: Wrapped) } var age: Int? = .none age = 10 age = .some(20) age = nil var age: Int? = 10 var age0: Optional<Int> = Optional<Int>.some(10) var age1: Optional = .some(10) var age2 = Optional.some(10) var age3 = Optional(10) age = nil age3 = .none switch age { case let v?: print("some", v) case nil: print("none") } switch age { case let .some(v): print("some", v) case .none: print("none") } var age: Int? = nil var age0 = Optional<Int>.none var age1: Optional<Int> = .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
三、高級運算符
-
3.1、溢出運算符
Swift的算術運算符出現溢出時還會拋出運行時錯誤
Swift有溢出運算符(&+、&-、&*),用來支持溢出運算
Int8(有符號):
-128 ~ 127
;UInt8(無符號):0 ~ 255
-
在使用了溢出運算符,那么范圍就變成了一個圈,如下結果:
print(Int8.max &+ 1) // 結果是 128 &+ 1 = -128 print(Int8.min &- 1) // 結果是 -128 &- 1 = 127
提示:使用了溢出運算符,那么范圍就變成了一個圈 如:
UInt8(-128~127~128~127)
-
3.2、運算符重載
類、結構體 、枚舉 可以為現有的運算符提供自定義的實現,這個操作叫做:運算符重載class Point { var x: Int var y: Int init(x: Int,y:Int) { self.x = x self.y = y } static func + (p1: Point,p2: Point) -> Point { Point(x: p1.x + p2.x, y: p1.y + p2.y) } static func - (p1: Point,p2: Point) -> Point { Point(x: p1.x - p2.x, y: p1.y - p2.y) } static func += (p1: inout Point, p2: Point) { p1 = p1 + p2 } static func -= (p1: inout Point, p2: Point) { p1 = p1 - p2 } static prefix func ++ (p: inout Point) -> Point { p += Point(x: 1, y: 1) return p } static postfix func ++ (p: inout Point) -> Point { let tmp = p p += Point(x: 1, y: 1) return tmp } static func == (p1: Point, p2: Point) -> Bool { (p1.x == p2.x) && (p1.y == p2.y) } } let p1 = Point(x: 1, y: 2) let p2 = Point(x: 10, y:20) let p3 = p1 + p2 print(p3.x,p3.y)
提示:prefix代表前置,postfix代表后置
-
3.3、Equatable
-
要想得知2個實例是否等價,一般做法是遵守
Equatable
協議,重載==
運算符;與此同時,等價于重載了!=
運算符class Person: Equatable { var age: Int init(age: Int) { self.age = age } static func == (lhs: Person, rhs: Person) -> Bool { lhs.age == rhs.age } }
-
Swift為以下類型提供默認的Equatable 實現
-
沒有關聯類型的枚舉
enum Car { case Name case Price } let car1 = Car.Name let car2 = Car.Price print(car1 == car2) // false
-
只擁有遵守 Equatable 協議關聯類型的枚舉
enum Answer { case wrong(Int,String) case right }
提示:Int,String......都是遵守 Equatable 的
-
只擁有遵守 Equatable 協議存儲屬性的結構體
struct Point : Equatable { var x: Int, y: Int } var p1 = Point(x: 10, y: 20) var p2 = Point(x: 11, y: 22) print(p1 == p2) // false print(p1 != p2) // true
-
-
引用類型比較存儲的地址值是否相等(是否引用著同一個對象),使用恒等運算符
===
、!==
提示:
===
、!==
用于引用類型,比較指向的內存地址是否是同一個
-
-
3.4、Comparable
-
score大的比較大,若score相等,age小的比較大
struct Student : Comparable { var age: Int var score: Int init(score: Int, age: Int) { self.score = score self.age = age } static func < (lhs: Student, rhs: Student) -> Bool { (lhs.score < rhs.score) || (lhs.score == rhs.score && lhs.age > rhs.age) } static func > (lhs: Student, rhs: Student) -> Bool { (lhs.score > rhs.score) || (lhs.score == rhs.score && lhs.age < rhs.age) } static func <= (lhs: Student, rhs: Student) -> Bool { !(lhs > rhs) } static func >= (lhs: Student, rhs: Student) -> Bool { !(lhs < rhs) } }
-
要想比較2個實例的大小,一般做法是:
(1)、遵守 Comparable 協議
-
(2)、重載相應的運算符
var stu1 = Student(score: 100, age: 20) var stu2 = Student(score: 98, age: 18) var stu3 = Student(score: 100, age: 20) print(stu1 > stu2) // true print(stu1 >= stu2) // true print(stu1 >= stu3) // true print(stu1 <= stu3) // true print(stu2 < stu1) // true print(stu2 <= stu1) // true
-
-
3.5、自定義運算符
-
可以自定義新的運算符:在全局作用域使用
operator
進行聲明prefix operator 前綴運算符
postfix operator 后綴運算符
infix operator 中綴運算符 : 優先級組prefix operator +++ prefix func +++ (_ i: inout Int) { i += 2 } var age = 3 print(+++age)
優先級組
precedencegroup 優先級組 { associativity: 結合性(left\right\none) higherThan: 比誰的優先級高 lowerThan: 比誰的優先級低 assignment: true代表在可選鏈操作中擁有跟賦值運算符一樣的優先級 } prefix operator +++ infix operator +- : PlusMinusPrecedence precedencegroup PlusMinusPrecedence { associativity: none higherThan: AdditionPrecedence lowerThan: MultiplicationPrecedence assignment: true }
提示:
- associativity: 結合性(left\right\none)
- higherThan: 比誰的優先級高
- lowerThan: 比誰的優先級低
- assignment: true代表在可選鏈操作中擁有跟賦值運算符一樣的優先級
Apple文檔:運算符參考
-
自定義 運算符實例
prefix operator +++ infix operator +- : PlusMinusPrecedence precedencegroup PlusMinusPrecedence { associativity: none higherThan: AdditionPrecedence lowerThan: MultiplicationPrecedence assignment: true } struct Point { var x: Int, y: Int static prefix func +++ (point: inout Point) -> Point { point = Point(x: point.x + point.x, y: point.y + point.y) return point } static func +- (left: Point, right: Point) -> Point { return Point(x: left.x + right.x, y: left.y - right.y) } static func +- (left: Point?, right: Point) -> Point { print("+-") return Point(x: left?.x ?? 0 + right.x, y: left?.y ?? 0 - right.y) } } struct Person { var point: Point } var person: Person? = nil person?.point +- Point(x: 10, y: 20)
提示:
person?.point +- Point(x: 10, y: 20)
的+-
等同于=
-