iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 18
0

Closure

Closure,你可以聽到有人稱它為閉包,官方文件上是這樣解釋它:

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.

Closure 是一個獨立的程式碼區塊,可以被用來傳遞及使用,這句話是不是很耳熟,就很像前幾篇介紹的 Function 不是嗎?

確實他們很相似,但還是有些差別,讓我們來看看到底什是 Closure。

Closure Introduction

我們先來看看一個簡單的 Function:

func add(value1: Int, value2: Int) -> Int {
    return value1 + value2
}

print(add(value1: 5, value2: 3)) // 8

Closure 的語法:

{(參數列) -> 回傳值型別 in
	...
}

所以用 Closure 表達會像是這樣:

let add = {(value1: Int, value2: Int) -> Int in
    return value1 + value2
}

print(add(5, 3)) // 8

Closure 有個特性就是,他雖然是一個獨立的程式碼區塊,但又不像 Functions 這麼獨立,因為 Closure 並沒辦法真正獨立存在,他必須指派給一個常數或是變數,或是作為 Function 的參數,像上面這個範例一樣,將一個 Closure 指派給 add 常數。

Closure 類型

匿名 Functions

還記得之前在介紹 Functions 時,無回傳值 Functions 嗎?這類的 Functions 又可被轉換為 Closure。

func 說你好() {
    print("你好")
}

說你好() // "你好"
let 說你好 = { () -> () in
    print("你好")
}

說你好()

因為 Closure 無法單獨存在,所以必須指派給一個常數或變數。

還記得這種無回傳值的 Function 是一個 Void 類型的 Function 嗎?他其實是會回傳 () -> (),所以在 Closure 表達上他其實是可以被寫成這樣,但是其實還可以再更精簡:

let 說你好 = {
    print("你好")
}

說你好()

對於這種匿名 Functions,除了原本可以省略 return 的單行表達式,() -> () 也是可以被省略的喔,像這種 Closure 表達是不是看起來更簡潔一點了呢?

Closure 作為 Function 的參數

可以將 Closure 當作參數,傳入 Functions,來看下面這個範例:

func 說你好(action: () -> ()) {
    action()
}

說你好(action: {
    print("你好")
})

說你好() 的參數輸入,有一個 action 的參數,他的型態就是一個 Closure,然後這個 Function 中又會在呼叫這個 action(),所以我們在呼叫的時候,就可以針對這個 action() 去做特定的事情,但是這可以再被簡化:

func 說你好(action: () -> ()) {
    action()
}

說你好 {
    print("你好") // 你好
}

當這個 Closure 為最後一個參數時,我們就可以將這個參數標籤省略掉,直接使用 {},這種方式叫做 Trailing Closures,我們來看另外一個多個參數傳入的例子:

func 說你好(次數: Int, action: () -> ()) {
    for _ in 1...次數 {
        action()
    }
}

說你好(次數: 5) {
    print("你好")
}

/*
Prints:
你好
你好
你好
你好
你好
*/

這也是一個 Trailing Closures 的範例,Closure 一樣位於最後一個參數,所以 Closure 的參數標籤可以被省略,但是在這個 Closure 參數之前的參數都必須得像使用 Functions 照常輸入。

多參數 Closure

Functions 允許輸入多個參數,Closure 也不例外,我們來看一下下面的範例:

func 打招呼(姓名: String, 內容: (String, String) -> ()) {
    內容("很開心看見你!", "你好啊!\(姓名)")
}

打招呼(姓名: "安竹", 內容: { (話語1: String, 話語2: String) -> () in
    print(話語1 + 話語2) // 很開心看見你!你好啊!安竹
})

內容 這個參數被指派為一個 Closure,並且有兩個 String 的參數,所以在調用上 {},可以宣告兩個常數來承接這兩個 String,所以上面這個例子可以看到,Function 中再去呼叫內容,並傳遞兩個字串的參數,所以在呼叫打招呼()這個方法時,我建立了兩個常數話語1話語2,來承接,然而就可以在 Closure 中來使用這兩個常數。

當然,因為這也是 Trailing Closures,所以可以在簡化一點:

打招呼(姓名: "安竹") { (話語1: String, 話語2: String) -> () in
    print(話語1 + 話語2)
}

你以為結束了嗎?還可以在簡化一點!

打招呼(姓名: "安竹") { (話語1, 話語2) -> () in
    print(話語1 + 話語2)
}

我們可以省略掉在 Closure 中兩個參數的型別註記,這是貼心的 Swift 幫我們自動推斷的,Swift 從被呼叫 Function 中來去推斷 Closure 參數的型別或是回傳值的型別,這種方式叫做 Inferring Type From Context

然而 Closure 中宣告的參數被寫在 () 中,這個其實也是可以被省略的,回傳值的註記也是,所以又可以被簡化成這樣:

打招呼(姓名: "安竹") { 話語1, 話語2 in
    print(話語1 + 話語2)
}

但這也不是最省略的方式,在 Closure 中,你可使用 $0$1 用來代表 Closure 的第一個參數以及第二個參數,當然也可能會有 $2,就依照建立 Function 時的 Closure 參數數量,所以可以用 $0 以及 $1 來省略在 Closure 中宣告常數,所以最後可以被最簡化成這樣:

打招呼(姓名: "安竹") {
    print($0 + $1)
}

上一篇
Day 17 | Swift Functions (2)
下一篇
Day 19 | Swift Enumerations
系列文
給我 30 天,給你一輩子:Swift 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言