---
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)
```
> 
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)")
```
> 
### 方法參數 - 內外標籤
* 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
* 雖然使用者可以透過 參數指名,但傳入的參數順序仍要按照方法設定,不可變換位置
> 
:::
:::
* 如果想要隱藏對外的引數標籤(完全隱藏),可使用 `_` 符號作為引數標籤,這樣使用起來更加方便
```swift=
// 使用 `_` 符號
func myFunction4(_ times: Int) {
for i in 0..<times {
print("Item: \(i)")
}
}
// 外部使用就不須使用 引數標籤
myFunction4(13)
```
:::warning
* 傳入方法內的參數有一特性:
就是傳入方法的參數,對於方法內部是一個常數(Constant),不可再被改變!當你嘗試改變傳入的參數,會導致編譯不過
> 
:::
### 特殊參數 - 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)")
```
> 
:::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")
```
> 
### 可變數量參數
* 當你要設計一個可以隨意讓使用者輸入不確定長度的參數方法時,就可以用到可變數量參數;範例如下
```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)")
```
> 
:::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)
```
> 
### 巢狀方法
* 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)
```
> 
## 方法型態
每個方法其實都是一種特殊的型態(就像在 JVM 中每個方法都有不同的簽名),我們可以用這種形態來創建一個變數
### 變數 & 方法型態
1. 方法其實是一種型態,我們可以指定變數的型態,並賦予變數的數值為方法
> 使用該型態只需要使用 `()` 符號,它就會自動運行該型態
```swift=
// 指定變數型態 型態 (String) -> Void
// 賦予變數的數值為方法
var funcSign : (String) -> Void = showInfo
// 方法型態 (String) -> Void
func showInfo(_ info: String) {
print("\(info)")
}
// 運行型態
funcSign("Hello")
```
> 
2. 同上,不過改變了接收、返回的簽名
```swift=
var funSign2 : (Int, Int) -> String = addNum
func addNum(_ value1: Int, _ value2: Int) -> String {
return "\(value1 + value2)"
}
// 運行型態
print(funSign2(1234, 7183))
```
> 
### 參數 & 方法型態
* 方法 A 的參數也可以接收另一個 方法型態
```swift=
func callFunction(_ funcSign : (String) -> Void) {
funcSign("Showing function.")
}
func showInfo(_ info: String) {
print("\(info)")
}
callFunction(showInfo)
```
> 
:::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))
```
> 
## 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`