---
# System prepended metadata

title: Swift - 函式、列舉
tags: [iOS]

---

---
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`
