--- title: 'Swift - Optional 型態、泛型、Error' disqus: kyleAlien --- Swift - Optional 型態、泛型、Error === ## Overview of Content [TOC] ## Optional - 概述 Optional 是 Swift 安全設計的類型,它可以 **讓 Swift 的型態具有安全性**,能讓型態有可為 nil 的 **可能性** > 也就代表了每個型態 (包括自定義) 都可因為有 Optional 而變成兩種型態; > > eg. `String` 因為 Optional 可拓展為 `String?` :::info * **為啥有了 `Optional` 就代表安全性**? `Optional` 會讓型態具有可 nil 的性質,而這個特性會在源碼編譯期間就檢查,如果不通過,則會無法編譯成功 ::: :::warning * **為何需要 `Optional`** ? 簡單來說,是為了更清晰的表達狀態,因為 **++空++ 並不代表為 ++無(`nil`)++**… 就像是我可以每天看 0(空) 本書,但這並不代表我沒有書(`nil`) ```swift= struct Person { var readBook: Int } struct Person2 { var readBook: Int? } // 必須看書,就算是 0 本也要看 Person(readBook: 10) // 代表 readBook 並非一個必須選項,可以完全不看 Person2() // okay ``` ::: ### 使用 Optional - 安全使用 `?` * 宣告 `Optional` 非常簡單,只需要在型態後面加上 `?` 即可;型態如下 ```swift= var 變數: 型態? ``` * **使用 `?` 關鍵符號**:如同 Kotlin,在使用可能為 nil 值的型態時,先幫我們判斷是否為 nil 如果 nil 則不往下執行 ```swift= var myValue: Int? // 因為 myValue 為 nil,所以不執行 hashValue print("Option value: \(myValue?.hashValue)") myValue = 333 // 因為 myValue 已由值,所以執行 hashValue print("Option value: \(myValue?.hashValue)") ``` ### 安全解開 Optional - `if let` / `guard let` * 而取出 `Optional` 則是另外一個關鍵,必須先解開 `Optional` 後才可以安全使用;**如果沒有解開,則類型則是 `Optional`** ```swift= var myValue: Int? myValue = 100 print("Option value: \(myValue)") ``` > ![](https://hackmd.io/_uploads/BJeWlk8w2.png) * **要解開 `Optional` 的型態有幾種方式** 1. **使用 `if let` 關鍵字**:因為 Optional 代表了兩種狀態,所以使用 `if let` 可以很好的表達兩種狀態 ```swift= var myValue: Int? myValue = 100 myValue?.hashValue // 這裡使用 if let 代表兩種狀態 if let getMyValue = myValue { // 這裡比較特別,使用解開 Optional 後的變數! print("value: \(getMyValue)") } else { print("value: was nil") } ``` :::info * `if let` 的括號 `{ }` 內部可以接收到解開的 Optional 的變數 ::: 2. **使用 `guard let` 關鍵字**:它與 `if let` 的差異不大,**但 `guard let` 如果解開後為空(`nil`),則離開當前作用範圍** > **離開方式可以用拋出(之後說明)、返回** ```swift= func showValue(value: Int?) { guard let times = value else { print("value is nil") return } for i in 1 ... times { print("value: \(i)") } } showValue(value: nil) showValue(value: 5) ``` > ![](https://hackmd.io/_uploads/S1QDS1Uwh.png) :::success * `guard let` 的括號 `{ }` 內部 ++不可以++ 接收到解開的 Optional 的變數,外部才可用到 * `guard let` 最常與 `return` 一起使用,它 **可以保證後面的程式碼安全的被執行**,而不用擔心為 nil ::: ### 強制解開/自動解開 Optional - `!` * 有時我們遇到強制解開 `Optional` 的狀況(當然... 盡量少使用),如果需要 **強制解開 `Optional` 就需使用 `!` 關鍵符號** :::warning * 原本使用 `Optional` 那編譯器會為我們檢查,但如果使用 `!` 的話,那 **編譯器會將這個安全責任交給使用者** 如果有問題則會拋出 Error > ![](https://hackmd.io/_uploads/HkT1YyIw2.png) ::: ```swift= var myValue: Int? print("Option value: \(myValue?.hashValue)") myValue = 333 print("Option value: \(myValue!.hashValue)") ``` > ![](https://hackmd.io/_uploads/HJBpdyUDh.png) * 強制解開(`!`)還有另一種用法:**自動解開(`implicit unwrapping`)**,在宣告類型時在後面加上 `!` 符號;格式如下 ```swift= var 型態: String! ``` 範例如下: ```swift= var myStr: String! print("myStr: \(myStr)") // 但仍要注意,如果本為 nil 一樣無法解開 //myStr += "Hello world" //print("myStr: \(myStr)") // 使用時不需要特地解開,就可使用 myStr = "" myStr += "Hello world" print("myStr: \(myStr)") ``` > ![](https://hackmd.io/_uploads/rkD961LDh.png) ## Optional 特殊用法 ### Optional 建構函數 - `init?` * 如果想在建構函數時做判斷,如果不符合條件則不建立物件,這時就可以使用 `init?` (初始化失敗函數 `failable initializer`) > 它與 throw 比較起來,較為溫和不暴力~ ```swift= struct Shape { var line: Int init?(line: Int) { // 配合 guard 一起使用 guard line > 2 && line <= 9 else { // 不成立則返回 nil return nil } self.line = line } } let box = Shape(line: 4) print("box: \(box)") let triangle = Shape(line: 3) print("triangle: \(triangle)") let line = Shape(line: 2) print("line: \(line)") let mulLine = Shape(line: 10) print("mulLine: \(mulLine)") ``` > ![](https://hackmd.io/_uploads/BJyuGlUvh.png) ### 安全轉換 - `as?` * 當一個類需要向下轉型(轉型為某個子類),但不確定是否可以轉換成功時就可以使用安全轉換 `as?`;其格式如下 ```swift= 原類型 as? 目標類型 ``` 範例如下:搭配 `let if` (當然也可以搭配 `guard if` 使用) ```swift= class Person { var name: String init(name: String) { self.name = name } } class Child: Person { func sayHello() { print("He..l..lo") } } class Teenager: Person { func sayHelloWorld() { print("Yo yo~ hello world") } } let personList: [Person] = [Child(name: "Alien"), Teenager(name: "Kyle")] personList.forEach { person in print("name: \(person.name)") if let child = person as? Child { child.sayHello() } else if let teenager = person as? Teenager { teenager.sayHelloWorld() } print("\n") } ``` > ![](https://hackmd.io/_uploads/ByG-rxIwn.png) ## Error 協議 在 Swift 語言中,如果有錯誤要拋出是使用 Error,而 **Error 本身是協議**,它算是一種標誌協議(也就是沒有函數) > 而在 Java、Kotlin 語言中則是 Throwable ```swift= public protocol Error : Sendable { } ``` ### 自訂 Error * **自訂 Error**:相當容易,就是實作 `Error` 協議即可 ```swift= class MyError: Error { var errorMsg: String? } ``` ### 拋出錯誤 try-throws * **拋出錯誤**:Swift 在拋出部分有點像是 Java,是屬於可捕捉類型錯誤(需要聲明拋出);其格式如下 ```swift= // 使用 `throws` 關鍵字 func 函數名(參數) throws { //... do something } ``` 範例如下:配合 `guard` 判斷,並使用 `throw` 關鍵字拋出 ```swift= // 1. 定義該函數為會拋出函數 func showMsg(times: Int) throws { guard times > 1 else { var e = MyError() e.errorMsg = "Don't less than 1" // 2. 具體拋出的錯誤 throw e } for i in 1 ... times { print("msg: \(i)") } } ``` * 呼叫有聲明 `throws` 的函數,就需要使用 `try` 關鍵字 (**不管是否出錯,強制使用 `try` 關鍵字**) ```swift= try showMsg(times: 5) ``` > ![](https://hackmd.io/_uploads/H1idaxLP3.png) ### 捕捉錯誤 do-catch * 延續上個範例,如果需要捕捉錯誤則需要使用 `do`、`catch` 關鍵字;其格式如下 ```swift= do { // call functions } catch { // occur error } ``` 範例如下: ```swift= do { try showMsg(times: 5) try showMsg(times: -1) } catch { print("Occur error") } ``` > ![](https://hackmd.io/_uploads/rJgzReLDh.png) :::info * 如果使用 `catch` 並且沒有定義任何捕捉類型,那 Swift 有提供一個 **`error` 參數**(在 `catch` 範圍內)可使用 ```swift= do { try showMsg(times: 5) try showMsg(times: -1) } catch { // 使用預設參數 catch print("Occur error=\(error)") } ``` ::: * **捕捉指定 Error**;有兩種使用方式,其使用格式如下 ```swift= // 1. 捕捉指定 Error, is + catch 判斷類型 do { // call functions } is catch 類型 { // occur error } // ------------------------------------ // 2. 捕捉指定 Error, catch let + as 強制轉型 do { // call functions } catch let 變數 as 類型 { // 在這裡就可以使用變數 } ``` 範例如下: 1. 自訂 Error:這裡沒有修改 ```swift= class MyError: Error { var errorMsg: String? } ``` 2. 拋出錯誤的函數:在這裡多添加一種拋出類型 ```swift= func showMsg(times: Int) throws { guard times > 1 else { var e = MyError() e.errorMsg = "Don't less than 1" throw e } if times > 10{ throw CancellationError() } for i in 1 ... times { print("msg: \(i)") } } ``` 3. 捕捉指定錯誤: ```swift= do { try showMsg(times: -1) } catch let myError as MyError { print("Occur error: \(myError.errorMsg ?? "")") } catch { print("Occur error=\(error)") } ``` > ![](https://hackmd.io/_uploads/S1fCxW8vh.png) ## 泛型 - 概述 泛型簡單來說就是可以 **參數化類型**(把類型作為參數來使用),可以用在 **類、結構、介面、方法** 之上;那為何需要泛型? ```swift= // 以下這種寫法是不是很麻煩?要為每個類型(Int, Double, CShort...)寫 add 函數 // // 泛型可以避免過多的 Function Overload func add(a: Int, b: Int) -> Int { return a + b } func add(a: Double, b: Double) -> Double { return a + b } func add(a: CShort, b: CShort) -> CShort { return a + b } ``` 簡單來說就是為了 **靈活複用程式碼**,**泛型也可以稱為是一個程式碼模板** :::info * Swift 中的集合就是使用了泛型 ::: ### 泛型使用 - Queue * 泛型在程式中是使用一個符號來表示,這個符號可以是 A、B、C... 甚至是一個字串,再加上 `<>` 符號;泛型使用的格式如下 ```swift= // 以下用結構來說明,類 (Class) 也可以這樣使用 // 宣告時 struct 結構名<泛型符號> { var value: 泛型符號 } // 使用時 let 參數名 = 結構名<類型>(value: 類型數據) ``` 範例如下 ```swift= struct MyStruct<TYPE> { var value: TYPE func showValue() { print("value: \(value)") } } var s1 = MyStruct<String>(value: "Hello") s1.showValue() var s2 = MyStruct<Int>(value: 111) s2.showValue() ``` > ![](https://hackmd.io/_uploads/B1QS5SuP3.png) :::warning * 如果給予的參數,與宣告的泛型類型不同,則無法通過編譯 > ![](https://hackmd.io/_uploads/rJRz9HuD3.png) ::: :::success * Swift 自動推導 Swift 可以根據你給予的參數自動幫你推導你當前使用的泛型,這樣可以讓程式碼更整潔(使不使用依照團隊決議決定) ```swift= var s3 = MyStruct(value: "Hello") s3.showValue() var s4 = MyStruct(value: 111) s4.showValue() ``` ::: * **手做泛型佇列(Queue)** > 同上所述,泛型符號可以隨意使用,這邊改成用 `T` 其意也是帶表 Type ```swift= class HandleQueue<T> { private var array = [T] () var size: Int { get { return array.count } } func push(_ item: T) { array.append(item) } func pop() -> T? { if array.count <= 0 { return nil } else { var tmp = array.first array.remove(at: 0) return tmp } } } ``` 使用手做的泛型佇列:使用兩種不同類型的數據 * Int 類型佇列 ```swift= var intQueue = HandleQueue<Int>() for i in 1 ... 3{ intQueue.push(i) print("after push, intQueue size: \(intQueue.size)") } print("intQueue pop: \(intQueue.pop()!)") print("intQueue pop: \(intQueue.pop()!)") print("intQueue pop: \(intQueue.pop()!)") ``` * String 類型佇列 ```swift= var strQueue = HandleQueue<String>() for i in 1 ... 3{ strQueue.push("Hello Generic \(i)") print("after push, strQueue size: \(intQueue.size)") } print("strQueue pop: \(strQueue.pop()!)") print("strQueue pop: \(strQueue.pop()!)") print("strQueue pop: \(strQueue.pop()!)") ``` ### 泛型方法 * 泛型不一定要存在類或結構中,像是 **泛型方法** 就可以 **單獨存在**(也可以放在頂層做頂層泛型函數);其格式稍微不同,如下 ```swift= // 無返回 func 函數名<泛型符號>(_ param: 泛型符號, _ param: 泛型符號) { // do something } // 有返回 func 函數名<泛型符號>(_ param: 泛型符號, _ param: 泛型符號) -> 泛型符號 { // do something } ``` 範例如下 ```swift= struct MyStruct { static func switchValuse<T>(_ a: inout T, _ b: inout T) { var tmp = a a = b b = tmp } } var aValue = 10 var bValue = 999 print("Before switch, a=\(aValue), b=\(bValue)") MyStruct.switchValuse(&aValue, &bValue) print("After switch, a=\(aValue), b=\(bValue)") ``` > ![](https://hackmd.io/_uploads/HJ-UMU_Dn.png) ### 泛型限制 - Protocol * 我們可以使用 protocol 對泛型做限制,**只要是 protocol 子類的實作都可傳入**,這樣有什麼功能呢? **可以保證泛型可以使用 protocol 定義的方法!** 範例如下 1. 定義抽象 protocol:在這個 protocol 下的方法,就是泛型可使用的方法 ```swift= protocol IntProtocol { func getValue() -> Int } ``` 2. 實作抽象:這裡實作的方法,就是泛型經過限制之後會用到的方法 ```swift= struct MyStruct1: IntProtocol { func getValue() -> Int { return 100 } } struct MyStruct2: IntProtocol { func getValue() -> Int { return 100000 } } ``` 3. 泛型實作:這裡的泛型就要使用繼承的方式定義 `<泛型 : Protocol>` ```swift= func addInt<T: IntProtocol, T2: IntProtocol>(_ a: T, _ b: T2) -> Int { return a.getValue() + b.getValue() } ``` * 使用限制的泛型方法: 這裡的參數就必須是 IntProtocol 的子類 ```swift= let finalVal1 = addInt(MyStruct1(), MyStruct1()) print("Final val 1: \(finalVal1)") let finalVal2 = addInt(MyStruct2(), MyStruct2()) print("Final val 2: \(finalVal2)") let finalVal3 = addInt(MyStruct1(), MyStruct2()) print("Final val 1+2: \(finalVal3)") ``` > ![](https://hackmd.io/_uploads/ryv0HU_w3.png) ## Appendix & FAQ :::info ::: ###### tags: `iOS`