昨天我們深入了解了類別與結構,學會了如何將資料與功能有效組織起來。今天,我們要認識 Swift 中另一項更輕便、更靈活的利器:閉包(Closure)。
簡單來說,閉包就像一個沒有名字的函式,可以像變數一樣被儲存和傳遞。但它最神奇的地方在於天生自帶「記憶力」:它能「捕獲」周遭的變數與常數,就算原始的程式區塊已經消失,閉包依然記得當時的狀態,並在需要時取用。
Swift 中的閉包有多種形式,依用途與特性略有不同。以下是幾種常見閉包的比較整理:
種類 | 關鍵字 | 特點與使用情境 |
---|---|---|
一般閉包 | 無 | 最常見的閉包寫法,可捕獲外部變數 |
尾隨閉包 | 無 | 將閉包寫在函式呼叫括號外,讓程式碼更易讀 |
逃逸閉包 | @escaping |
閉包在函式執行結束後仍可被呼叫,例如非同步任務 |
自動閉包 | @autoclosure |
讓傳入的表達式延遲執行,常用於簡化語法或效能優化 |
閉包表達式可讓我們用簡潔的語法定義匿名函式,格式如下:
{ (參數列表) -> 返回值型別 in
// 執行的程式碼
}
例如,我們有一個函式用來加總兩個整數:
func addTwoInts(number1: Int, number2: Int) -> Int {
return number1 + number2
}
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("結果是:\(mathFunction(a, b))")
}
printMathResult(addTwoInts, 12, 85) // 印出 結果是:97
// 使用閉包表達式
printMathResult({ (a: Int, b: Int) -> Int in a + b }, 12, 85)
Swift 提供多種語法簡化方式,讓閉包更精簡:
printMathResult({ number1, number2 in return number1 + number2 }, 12, 85)
return
printMathResult({ number1, number2 in number1 + number2 }, 12, 85)
$0
, $1
取代參數名稱printMathResult({ $0 + $1 }, 12, 85)
printMathResult(+, 12, 85)
以上寫法功能完全相同,依可讀性與習慣選擇使用即可。
當函式的最後一個參數是閉包時,可以將閉包寫在函式括號外,提升可讀性。
func doSomething(message: String, completion: () -> Void) {
print(message)
completion()
}
未使用尾隨閉包時,寫法如下:
doSomething(message: "準備工作...", completion: {
print("完成!")
})
使用尾隨閉包後,程式碼變得更像一個自然的流程控制區塊:
doSomething(message: "準備工作...") {
print("完成!")
}
如果函式只有閉包一個參數,還可以省略括號:
func doSomethingElse(completion: () -> Void) {
completion()
}
doSomethingElse {
print("任務已執行。")
}
閉包可以捕捉並保存當時作用域中的變數,並在之後使用:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // 10
print(incrementByTen()) // 20
print(incrementByTen()) // 30
每個閉包都獨立維護自己的狀態,互不影響。
閉包是參考型別,若多個變數指向同一閉包,會共用狀態。
當閉包在函式執行完後才會被呼叫(例如非同步回呼),需加上 @escaping:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
逃逸閉包內要顯式使用 self
:
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure {
self.x = 100
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) // 10
completionHandlers.first?()
print(instance.x) // 100
這是 Swift 的一項安全特性。因為逃逸閉包會在函式結束後才被呼叫,它可能會「存活」得比它所屬的類別實體 (instance) 還要久。
Swift 強制你寫下 self
,是在提醒你:「注意!這裡可能會產生記憶體管理的循環參考(Retain Cycle)問題」。它強迫你意識到你正在閉包中捕獲 self
,需要多加留意。雖然現在還不用深入理解循環參考,但先記住這個「安全提醒」的設計用意。
自動閉包是一種語法糖,可以自動將表達式轉成閉包,讓函式呼叫看起來更自然。
func serve(customer customerProvider: () -> String) {
print("準備服務顧客:\(customerProvider())")
}
var customersInLine = ["小明", "小華"]
serve(customer: { customersInLine.remove(at: 0) })
func serveWithAutoclosure(customer customerProvider: @autoclosure () -> String) {
print("準備服務顧客:\(customerProvider())")
}
serveWithAutoclosure(customer: customersInLine.remove(at: 0)) // 更簡潔
注意:若要讓自動閉包同時也能逃逸,屬性需要同時宣告為
@autoclosure @escaping
。
今天我們攻克了 Swift 中最靈活也最強大的功能之一:閉包(Closure)!我們學會了閉包就是「匿名的函式」,能夠捕獲其上下文中的變數,並擁有從完整到極致簡潔的多種語法優化形式。
我們還掌握了如何使用尾隨閉包來提升程式碼可讀性,並認識了逃逸閉包與自動閉包這兩種處理特殊情境的進階用法。徹底理解閉包,是寫出優雅、高效率 Swift 程式碼的關鍵。
我們已經學會建立各種資料型別,但如何讓完全不同的型別(例如鳥
和飛機
)都能遵守同一套「規則」並保證擁有相同的功能(例如 fly()
)呢?
明天,我們就要來學習 Swift 中用來定義「行為藍圖」與「溝通合約」的超強工具:協定(Protocol)
!你將學會如何定義一套所有型別都必須遵守的規範,並一窺它在 App 開發中的強大應用,例如 iOS 開發者面試必問的 委派模式(Delegate Pattern)。
敬請期待《Day 14|Swift 協定導向程式設計:打造你的 Swift 程式規範!》