--- title: 'Swift - 函式、列舉' disqus: kyleAlien --- Swift - 函式、列舉 === ## Overview of Content [TOC] ## 函式 - 方法概述 Swift 允許使用者將特定行為、工作單元獨立出來,讓其可以被其他區塊呼叫,這就區塊是獨立運作,也稱為 **方法(Method)** **方法可以讓複雜的任務拆小、方便維護、管理** :::info * **方法 (Method)、函數 (Function)**? 簡單來區分 **方法**:**方法是物件導向對於獨立運作區塊(函式)的稱呼**;像是 Java, C, C++... 等等程式語言 **函數**:函數則是沒有物件導向概念的程式對於獨立運作區塊(函式)的稱呼;像是 C、Assemble... 等等語言 ::: :::warning * 如何規劃方法? 這沒有一個準則(基本上是經驗),但是可以依照一個概念:**法代表了一個功能,它只做一件事(完成一個目標)** ::: ### Swift 方法 - 格式 * Swift 是透過關鍵字 `func` 來讓編譯器認知到該程式區塊是函式,其基本格式如下: ```swift= // 函式格式 // 無返回函式 func 函式名(參數: 參數型態, ...) { // 函式本體 } // 有返回函式 -------------------------------------- func 函式名(參數: 參數型態, ...) -> 返回型態 { // 函式本體 return 返回值 } ``` * Swift 函示創建範例: 1. 無返回數據 ```swift= func myFunction(times: Int) { for i in 0..<times { print("Item: \(i)") } } myFunction(times: 13) ``` > ![](https://i.imgur.com/iFqhACl.png) 2. 有返回數據 ```swift= func myFunctionWithReturn(times: Int) -> Int { var res = 0 for i in 0..<times { res += i } return res } let res = myFunctionWithReturn(times: 13) print("res: \(res)") ``` > ![](https://i.imgur.com/X1mPDP2.png) ### 方法參數 - 內外標籤 * Swift 與其他高級程式語言不同的其中一點在於 **方法參數** 的描述,Swift 將方法參數細分為兩種描述(^1.^ 引數標籤, ^2.^ 參數標籤);其格式如下 ```swift= func 函式名(引數標籤 參數標籤: 參數型態, ...) { // 函式本體 } ``` 1. **引數標籤(argument label)**:如果有定義引數標籤那就有以下使用規範 * **對外**:使用者在呼叫方法時,必須透過引數標籤定義參數 * **對內**:在函數內部必須使用參數名稱 ```swift= func myFunction2(repeatTimes times: Int) { // 內部:使用參數標籤 for i in 0..<times { print("Item: \(i)") } } // 呼叫方式 // 外部:使用引數標籤 myFunction2(repeatTimes: 10) ``` 2. **參數名稱(parameter label)**:可以只定義參數名稱,這時編譯器會將 參數名稱 作為 引數標籤使用 對內、外都是使用 參數名稱呼叫 ```swift= // 只使用參數名稱 func myFunction3(times: Int) { // 對內使用參數名稱 for i in 0..<times { print("Item: \(i)") } } // 對外使用參數名稱 myFunction3(times: 13) ``` :::success * 這種分類可以讓,方法對於參數的定義更加清晰 :::warning * 雖然使用者可以透過 參數指名,但傳入的參數順序仍要按照方法設定,不可變換位置 > ![](https://i.imgur.com/8IyYpTb.png) ::: ::: * 如果想要隱藏對外的引數標籤(完全隱藏),可使用 `_` 符號作為引數標籤,這樣使用起來更加方便 ```swift= // 使用 `_` 符號 func myFunction4(_ times: Int) { for i in 0..<times { print("Item: \(i)") } } // 外部使用就不須使用 引數標籤 myFunction4(13) ``` :::warning * 傳入方法內的參數有一特性: 就是傳入方法的參數,對於方法內部是一個常數(Constant),不可再被改變!當你嘗試改變傳入的參數,會導致編譯不過 > ![](https://i.imgur.com/DFRf9K2.png) ::: ### 特殊參數 - inout * 對於方法傳入的參數,其內外是使用不同的記憶體儲存位置,也就是 **內部改變該參數,也不會影響外部的參數** > 其實你在內部也沒辦法改變,因為傳入方法後的參數,等同於常數(Constant)無法改變 * Swift 有一個 **關鍵字 `inout` 就可改變傳入為參數等同於常數的狀況**,方法參數類型通過 `inout` 描述後,內外就是取用同一記憶體位置,也就是說內部的修改同時會影響外部;其使用上的改變如下 1. **內部**:方法參數類型使用 `inout` 描述 2. **外部**:呼叫方法時,需要使用 `&` 符號描述傳入的數據(跟 C 語言的指標類似) ```swift= func add(_ a: inout Int, _ b: Int) { a = a + b } var value = 100 add(&value, 200) print("value: \(value)") ``` > ![](https://i.imgur.com/DeCxVsV.png) :::info 傳入方法的數據不可以是 Constant (不能用 let 描述) ::: ### 預設參數 * 可以對方法的參數設定預設的值,有預設值後使用者就不必一定要設定該參數 ```swift= func mul(_ value: Int, mulNumber: Int = 10, otherMsg: String = "") { print("res(\(otherMsg)) = \(value * mulNumber)") } mul(88) mul(13, mulNumber: 34) mul(13 , mulNumber: 34, otherMsg: "13*34") ``` > ![](https://i.imgur.com/kI7TD7R.png) ### 可變數量參數 * 當你要設計一個可以隨意讓使用者輸入不確定長度的參數方法時,就可以用到可變數量參數;範例如下 ```swift= func average(res: inout Double, _ numbers: Double...) { var tmp = 0.0 for i in numbers { tmp += i } res = tmp / Double(numbers.count) } var result = 0.0 average(res: &result, 1, 10, 123, 66, 1123) print("average result: \(result)") ``` > ![](https://i.imgur.com/LZdeVwL.png) :::info * 像是 Java 的可變參數有限定只能放在方法參數的末尾,而 Swift、Kotlin 這種新型語言則沒有這種規定 ```swift= func test(_ numbers: Int..., res: inout Int) { // ok } ``` ::: ### 回傳 Tuple * 方法可以回傳一個 Tuple(元組)類型,它有 Tuple 的特性,使用者接收到返回值後就可以針對需要的標籤裡 ```swift= func greetingFor(_ name: String) -> (hello: String, goodbye: String) { var resHello = "Hello ~ \(name), welcome to my home." var resGoodbye = "Goodbye ~ \(name), see you next time." return (resHello, resGoodbye) } var res = greetingFor("Alien") // 透過 標籤處理 print(res.hello) print(res.goodbye) ``` > ![](https://i.imgur.com/aJaED3a.png) ### 巢狀方法 * Swift 可以有巢狀方法(方法內的方法),這些巢狀方法有使用範圍的限制,限制只能在該函數內使用 ```swift= func myNestFunction(_ times: Int) { // 只能在 內部 使用的函數 func NestFunc(_ msg: String) { print("Nest --> \(msg)") } for i in 0..<times { NestFunc("\(i)") } for j in (0..<times).reversed() { NestFunc("\(j)") } } myNestFunction(5) ``` > ![](https://i.imgur.com/eX8B1FW.png) ## 方法型態 每個方法其實都是一種特殊的型態(就像在 JVM 中每個方法都有不同的簽名),我們可以用這種形態來創建一個變數 ### 變數 & 方法型態 1. 方法其實是一種型態,我們可以指定變數的型態,並賦予變數的數值為方法 > 使用該型態只需要使用 `()` 符號,它就會自動運行該型態 ```swift= // 指定變數型態 型態 (String) -> Void // 賦予變數的數值為方法 var funcSign : (String) -> Void = showInfo // 方法型態 (String) -> Void func showInfo(_ info: String) { print("\(info)") } // 運行型態 funcSign("Hello") ``` > ![](https://i.imgur.com/XSjvBu7.png) 2. 同上,不過改變了接收、返回的簽名 ```swift= var funSign2 : (Int, Int) -> String = addNum func addNum(_ value1: Int, _ value2: Int) -> String { return "\(value1 + value2)" } // 運行型態 print(funSign2(1234, 7183)) ``` > ![](https://i.imgur.com/zyCEjzG.png) ### 參數 & 方法型態 * 方法 A 的參數也可以接收另一個 方法型態 ```swift= func callFunction(_ funcSign : (String) -> Void) { funcSign("Showing function.") } func showInfo(_ info: String) { print("\(info)") } callFunction(showInfo) ``` > ![](https://i.imgur.com/7LYNRuj.png) :::success * 可以使用 `typealias` 來簡化 方法型態 > 格式:typealias *參數名* = *方法型態* ```swift= // 給方法型態另一種別名 typealias showMsgFunc = (String) -> Void func callFunction2(_ funcSign : showMsgFunc) { funcSign("Showing function.") } callFunction2(showInfo) ``` ::: ### 返回方法類型 * 我們也可以返回一種方法類型讓使用者使用 ```swift= func add(_ a: Int, _ b: Int) -> Int { return a + b } func reduce(_ a: Int, _ b: Int) -> Int { return a - b } // 返回一個方法類型讓使用者調用 func getCalFunc(isAdd: Bool) -> (Int, Int) -> Int{ // 三元運算 return isAdd ? add : reduce } var addFunc = getCalFunc(isAdd: true) // 使用者調用 print(addFunc(1, 5)) var reduceFunc = getCalFunc(isAdd: false) // 使用者調用 print(reduceFunc(1, 5)) ``` > ![](https://i.imgur.com/pZ4Yn1i.png) ## Enum 列舉 Enum 列舉,可以將一系列有關係的資料合併到有個結構中管理,這樣可以有效的控制資料 ```swift= // 以下是一組有關聯性的資料,但是沒有掌控 let CLASS_NAME_1 : String = "Clz_1" let CLASS_NAME_2 : String = "Clz_2" let CLASS_NAME_3 : String = "Clz_3" // 使用 enum 管理資料後(這邊尚不完整) enum ClassName{ case Clz_1 case Clz_2 case Clz_3 } ``` ### Enum 列舉建立、使用 * **Enum 列舉建立**:enum 這種結構建立方式有多種,如下範例 1. **基礎 enum 結構** ```swift= enum BaseEnum { } ``` 2. **基礎 enum 結構加上 case 案例** ```swift= enum BaseEnumWithCase { case One case Two case Three } ``` 3. **enum case 中的關聯值(數據)** ```swift= enum Fruit { case Apple(prize: Int) case Banana(prize: Int) } ``` 4. **enum 可繼承 `Int`, `String`, `Float` 類**:如果 enum 繼承 Int,並沒有指定數據的話,預設是從第一個 case 為 0 開始往下數 ```swift= enum DefaultValue : Int { case Apple case Banana } print("\(DefaultValue.Apple.rawValue)") print("\(DefaultValue.Banana.rawValue)") ``` 5. **enum 可繼承 `Iterable` 介面,讓其成為可遍歷的類**: ```swift= enum IterableEnum : CaseIterable { case Red case Orange case Yello case Green case Blue case Purple } for color in IterableEnum.allCases { print("color of \(color)") } ``` :::warning 在 Java、Kotlin 中 enum 類預設就是可以遍歷的,但 Swift 中並非如此,更加謹慎 ::: 5. **enum 原始值,數據初始化** ```swift= enum RequestResult: String { case Success = "Request Success :)" case Failure = "Request Failure :(" } print("\(RequestResult.Success.rawValue)") print("\(RequestResult.Failure.rawValue)") ``` :::danger Enum 原始值、關聯值只能選其中一個 ::: * **Enum 列舉使用**: 1. Swift 會在區域範圍內自動推導 Enum:在 Enum 領域內不需要 `Enum.Value` 直接使用 `.Value` 範例如下 ```swift= enum RequestResult: String { case Success = "Request Success :)" case Failure = "Request Failure :(" } var result: RequestResult = .Failure func setResult(_ newResult: RequestResult) { result = newResult } setResult(.Success) ``` 2. **switch 使用 Enum 時,必須處理全部狀況,如果沒全部處理則需要使用 default**(在 switch 內也會自動推倒) ```swift= enum RequestResult: String { case Success = "Request Success :)" case Failure = "Request Failure :(" } var result: RequestResult = .Failure switch result { case .Failure: print("Failure") case .Success: print("Success") } ``` ## Appendix & FAQ :::info ::: ###### tags: `iOS`