---
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)")
```
> 
* **要解開 `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)
```
> 
:::success
* `guard let` 的括號 `{ }` 內部 ++不可以++ 接收到解開的 Optional 的變數,外部才可用到
* `guard let` 最常與 `return` 一起使用,它 **可以保證後面的程式碼安全的被執行**,而不用擔心為 nil
:::
### 強制解開/自動解開 Optional - `!`
* 有時我們遇到強制解開 `Optional` 的狀況(當然... 盡量少使用),如果需要 **強制解開 `Optional` 就需使用 `!` 關鍵符號**
:::warning
* 原本使用 `Optional` 那編譯器會為我們檢查,但如果使用 `!` 的話,那 **編譯器會將這個安全責任交給使用者**
如果有問題則會拋出 Error
> 
:::
```swift=
var myValue: Int?
print("Option value: \(myValue?.hashValue)")
myValue = 333
print("Option value: \(myValue!.hashValue)")
```
> 
* 強制解開(`!`)還有另一種用法:**自動解開(`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)")
```
> 
## 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)")
```
> 
### 安全轉換 - `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")
}
```
> 
## 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)
```
> 
### 捕捉錯誤 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")
}
```
> 
:::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)")
}
```
> 
## 泛型 - 概述
泛型簡單來說就是可以 **參數化類型**(把類型作為參數來使用),可以用在 **類、結構、介面、方法** 之上;那為何需要泛型?
```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()
```
> 
:::warning
* 如果給予的參數,與宣告的泛型類型不同,則無法通過編譯
> 
:::
:::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)")
```
> 
### 泛型限制 - 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)")
```
> 
## Appendix & FAQ
:::info
:::
###### tags: `iOS`