(Swift) Closure 學習筆記

Photo by Cristina Gottardi on Unsplash

Var 變數也能存取 Function,這時候變數的型別就會是 Function 型別

一般的 Function (沒有回傳值)

func buyIphone(model: String, size: String) {
print("你買了\(model) \(size) 寸")
}

buyIphone(model: "iphone14 pro", size: "5.8")
// 你買了iphone14 pro 5.8 寸

相等的寫法

func buyIphone(model: String, size: String) -> () {
print("你買了\(model) \(size) 寸")
}

func buyIphone(model: String, size: String) -> Void {
print("你買了\(model) \(size) 寸")
}

將變數存取 buyIphone,我們就可以發現 buy1 的型別就會是 Function 型別

let buy1: (String, String) -> () = buyIphone
buy1("iphone14 pro", "5.8")
// 你買了iphone14 pro 5.8 寸

那如果將 buyIphone 加上括號呢? buyIphone()

let buy1: (String, String) -> () = buyIphone()

這時候會跳出錯誤,當我們寫出 buyIphone() 時,實際上是在呼叫 buyIphone 這個 Function,且我們也少了兩個參數。

那改成下方寫法應該就不會出錯了吧?

let buy1: (String, String) -> () = buyIphone(model: "iphone14 pro", size: "5.8")

那為什麼還是會出錯呢?答案其實就是剛剛有提到的,因為寫 buyIphone() 是呼叫函數,但是我們的函數是沒有回傳值的,所以類型是不匹配的。

如何寫才不會出錯

let buy1 = buyIphone(model: "iphone14 pro", size: "5.8")
print(buy1)
// ()

這時會發現 print(buy1)得到 (),原因就在於 buyIphone() 是沒有回傳值的

接下來相等的寫法 buy2

let buy1: (String, String) -> () = buyIphone
buy1("iphone14 pro", "5.8")
// 你買了iphone14 pro 5.8 寸

let buy2 = buyIphone
buy2("iphone14 pro", "5.8")
// 你買了iphone14 pro 5.8 寸

有回傳值的 Function

func buyIphone(model: String, size: String) -> String {
return "你買了\(model) \(size) 寸"
}

let buy1 = buyIphone // (String, String) -> String
print(buy1)
// (Function)

let buy2 = buyIphone(model: "iphone14 pro", size: "5.8") // 你買了iphone14 pro 5.8 寸
print(buy2)
// 你買了iphone14 pro 5.8 寸

() 就是 呼叫 Function,也就是上面不斷提到的,所以它會有回傳值賦予給 buy2

化身參數的 Function

// model 型別為 String 
func buyIphone(model: String) -> String {
return "你買了\(model) "
}
// model 型別為 () -> String
func buyIphone(model: () -> String) -> String {
return "你買了\(model()) "
}

使用 buyIphone 函數

func buyIphone(model: () -> String) -> String {
return "你買了\(model()) "
}

定義一個無參數,且回傳值為 String 的 Function 傳入 buyIphone

func iphone14() -> String {
return "iphone14"
}

buyIphone(model: iphone14)

接受參數的 function 型別

func buyIphone(model: (Int) -> String) -> String {
return "你買了\(model(3)) "
}

func iphone14(count: Int) -> String {
return "\(count) 隻 iphone14"
}
buyIphone(model: iphone14)

但程式這樣設計會有一點麻煩,若是要購買其他商品,就必須定義多個 Function

func iphone14() -> String {
return "iphone14"
}

func iphone15() -> String {
return "iphone15"
}

buyIphone(model: iphone14)
buyIphone(model: iphone15)

Closure

我們可以使用 Closure 解決上述的問題,那到底該怎麼做呢?將 buyIphone 中的 model 回傳值,改成下方的 closure 表達式。

buyIphone(model: { () -> String in
return "iphone14"
})

這時候你就不需要定義 Function,就能傳入任何的產品

buyIphone(model: { () -> String in
return "iphone14"
})
buyIphone(model: { () -> String in
return "iphone14 Pro"
})
buyIphone(model: { () -> String in
return "iphone14 Pro Max"
})

更簡潔的寫法,省略回傳的 型別 與 in

buyIphone(model: { 
return "iphone14"
})

在 Swift 中若是回傳的程式碼只有一行時,可以省略 return

buyIphone(model: { 
"iphone14"
})

最終還能進一步簡化程式

buyIphone {
"iphone14"
}

但是這是有一些限制的,可以看下方的 trailing closure 說明。

Trailing closure

若是 Function 中的參數,在最右邊為 Function 型別時,能夠有更佳簡潔的寫法,按下 Enter 試試看!

func buyIphone(count: Int, model: () -> String) -> String {
return "你買了\(model()) "
}

buyIphone(count: 3) {
"iphone14"
}

這時會發現參數 model 會消失,以及 ()也移動了。

來看看 Function 型別不為最右邊時的情況

func buyIphone(model: () -> String, count: Int) -> String {
return "你買了 \(count) 隻 \(model()) "
}

buyIphone(model: {
"iphone14"
}, count: 3)

這時的 buyIphone 就會變得有點不直觀了,所以記得當如果只有一個參數為 Function 型別時,記得放在最右邊。

接受參數的 Closure

func buyIphone(count: Int, model: (Int, String) -> Void) {
for i in 1...count {
model(i, "Iphone")
}
}

Closure 的寫法

buyIphone(count: 3, model: { (count: Int, model: String) -> Void in
print("今年買了 \(count) 次 \(model)")
})

簡化後面的 Void

buyIphone(count: 3, model: { (count: Int, model: String) in
print("今年買了 \(count) 次 \(model)")
})

簡化前面的參數

buyIphone(count: 3, model: { count, model in
print("今年買了 \(count) 次 \(model)")
})

簡化 model

buyIphone(count: 3) { count, model in
print("今年買了 \(count) 次 \(model)")
}

進一步簡化 Closure 參數

buyIphone(count: 3) {
print("今年買了 \($0) 次 \($1)")
}

$0 代表第一個參數,$1 就代表第二個參數

使用 forEach 來加深印象

let names = ["Jason", "Peter", "Kevin"]

names.forEach { name in
print(name)
}

names.forEach {
print($0)
}

使用 Closure 的副作用

先嘗試輸入以下程式碼

class Order {
var name = "Jason"
var model: (() -> Void)!

func buyIphone(buyAction: () -> Void) {
buyAction()
model = buyAction
}
}

Error message

會出現這個錯誤的原因,就在於 Function buyIphone() 中的 buyAction 型別為 function 型別,function 型別的參數是無法在 buyIphone Function 外做使用的,若要做使用的話,就必須加上 @escaping ,這樣一來你的參數就能在 buyIphone() 結束後繼續做使用。

escaping : 逃脫。可讓 closure 從 function 逃脫,function 執行後仍可繼續使用。

//  swift 修正後的 
class Order {
var name = "Jason"
var model: (() -> Void)!

func buyIphone(buyAction: @escaping () -> Void) {
buyAction()
model = buyAction
}
}

var order = Order()
order.buyIphone {
print("IphoneXR")
}

order.model()

@escaping 的副作用:

存取自己的屬性或方法時須加 self

class Order {
var name = "Jason"
var model: (() -> Void)!


func buyIphone(buyAction: @escaping () -> Void) {
buyAction()
model = buyAction
}

}

class Customer {
var name = "Jason"
var order = Order()

func myOrder() {
order.buyIphone {
print("\(name) buy IphoneXR")
}
}
}

Error message

有兩種修正的方法:

第一個:

直接在 name 前面加上 self


func myOrder() {
order.buyIphone {
print("\(self.name) buy IphoneXR")
}
}

第二個:

在 { 旁加上 [self] in ,這樣在這使用到的屬性都無需加上 self.

    func myOrder() {
order.buyIphone { [self] in
print("\(name) buy IphoneXR")
}
}

⭐️還有一種情況需要注意

當你將 參數的型別設為 Function 型別 optional 時,Swift 將認定此參數是 @escaping 這樣一來你就必須加上 self 或是 [self] in

class Order {
var name = "Jason"

func buyIphone(buyAction: (() -> Void)?) {
if let buyAction = buyAction {
buyAction()
}
}
}

class Customer {
var name = "Jason"
var order = Order()

func myOrder() {
order.buyIphone {
print("\(self.name) buy IphoneXR")
}
}
}

@autoclosure

先來看一下原本的程式碼

func buyIphone(model: () -> String) -> String {
return "你買了\(model()) "
}

buyIphone {
"IphoneXS"
}

() -> String 前面加上 @autoclosure

func buyIphone(model: @autoclosure () -> String) -> String {
return "你買了\(model()) "
}

buyIphone(model: "IphoneX")

現在要傳入的就會變成 String,它就能自動變成型別 () -> String 的 closure。

使用 @autoclosure 的好處,傳入的參數不一定會執行,或是傳入的參數延後執行

Ex:

func processResult(_ result: @autoclosure () -> Int) {
if result() > 0 {
print("Positive result")
} else {
print("Non-positive result")
}
}

processResult(5) // 輸出: Positive result
processResult(-3) // 輸出: Non-positive result

@autoclosure 還能夠與 @escaping 結合(在前面加上),且搭配的 function 型別必須沒有參數,因此 () -> Int可以,但 (Int) -> Int 不行

Reference

--

--