> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/swift/type/category/basic/closure/escaping.md).

# escaping closure

{% tabs %}
{% tab title="📘 手冊" %}

* [ ] :orange\_book: [Escaping Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID546) (Swift)
* [ ] :orange\_book: [Strong Reference Cycles for Closures](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID56) (Swift)
* [ ] :orange\_book: [Resolving Strong Reference Cycles for Closures](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID57) (Swift)
  {% endtab %}

{% tab title="📗 文章" %}

* [ ] [Swift @noescape 與 @escaping](https://www.jianshu.com/p/9fb444e88d26) (簡書)
* [ ] [Escaping Closures In Swift](https://learnappmaking.com/escaping-closures-swift/) - LearnAppMaking
  {% endtab %}

{% tab title="👥 相關" %}

* [⛔️ escaping closure captures mutating 'self' parameter](/ios/compiler-errors/escaping-closure-captures-mutating-self-parameter.md)
  {% endtab %}
  {% endtabs %}

## 解說 <a href="#explain" id="explain"></a>

* :point\_right: 本例改編自：官方 [Escaping Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID546) (Swift)
* :floppy\_disk: [replit](https://replit.com/@pegasusroe/escaping-closure#main.swift)

{% tabs %}
{% tab title="程式碼" %}

```swift
/*
    ⭐️ type alias 
    =============

    為了方便起見，後面統一將「不須傳入參數、也沒有任何回傳」的函數稱為 `Task`，
    這樣可以讓程式看起來更清楚、簡潔。
*/
typealias Task = () -> Void

// ⭐️ 將「未來」才要執行的工作（函數）放在這裡
var tasks: [Task] = []

/*
    ⭐️ non-escaping closure
    =======================

    這是一個單純的函數，不管傳入什麼工作(task)，都直接執行它(task())，
    所以當 doItNow 執行完畢，task 的生命週期也會隨著 doItNow 的結束而結束。

    像這樣的函數(task)，我們就稱為 "non-escaping" closure。
    一般的 closure 參數，如果沒有特別標示，都是屬於這種。
*/
func doItNow(task: Task) {
    task()
}

/*
    ⭐️ escaping closure
    ===================

    這裡傳入的工作(task)，是一個函數，它並沒有在 doItLater 這個函數
    內部執行（註：執行一個函數必須加 "()"，例如："task()"），而只是
    把 task 存到「未來要執行的工作」(tasks) 中。

    因為 doItLater 函數只做一件事，就是把 task 存到 tasks 中，
    所以直到 doItLater 執行完畢，task 這個函數都沒有真的執行過，
    不只如此，doItLater 執行完最後一行後，生命週期就會結束，但是
    task 這個函數不僅沒有結束，而且還繼續存活在 tasks 這個陣列中。

    這時會產生一個現象：

        「傳入的參數(task)存活得比處理它的函數(doItLater)還長」

    這時我們稱這個參數為 "escaping closure"，在 Swift 的語法上
    必須標示 "@escaping" 這個關鍵字，不然會產生 compiler 錯誤。
*/
func doItLater(task: @escaping Task) {
    tasks.append(task)          // 執行完此行，doItLater 的生命週期就會結束，
}                               // 但 task 還會存活下來❗️


// 用來幫助說明 @escaping closure 這個概念的物件類別
class A {
    
    // 物件屬性
    var x = 10

    // 物件方法
    func changeX() {

        /*
            將 { self.x = 100 } 這件事，放到 tasks 陣列中，
            以後再做 (⭐️ 所以 x = 100 還不會真的發生❗️)。
        */
        doItLater { self.x = 100 }

        // 現在馬上做 { x = 200 } 這件事。
        doItNow { x = 200 }

    }
}

// 測試
let a = A()
a.changeX()
print(a.x)                  // 200


tasks.first?()              // ⭐️ 現在執行儲存起來的第一個工作({ self.x = 100 })
print(a.x)                  // 100
```

{% endtab %}

{% tab title="📙 相關說明" %}

* :green\_book: [5 Ways to Use typealias in Swift](https://betterprogramming.pub/5-ways-to-use-type-alias-in-swift-45ddce3cc941)
* :orange\_book: [typealias](https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#ID327) (Swift)
* :orange\_book: [Trailing Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID102) (Swift)
* :orange\_book: [array.first](https://developer.apple.com/documentation/swift/array/1689165-first) (Swift)
  {% endtab %}
  {% endtabs %}

## ⭐️ 抓住物件 <a href="#capturing" id="capturing"></a>

{% hint style="danger" %}
注意看：

同樣是「**修改屬性 x**」這樣一個動作，**`doItLater`** 跟 **`doItNow`** 的寫法就不同：

* **doItNow** 寫的是： { x = 200 }
* **doItLater** 寫的是： { **self**.x = 100 } ，如果不指定 **self** 會出現 compiler 錯誤。
  {% endhint %}

### 為什麼有這樣的區別呢 :question:  <a href="#why" id="why"></a>

這牽涉到**物件參照**(object reference)的問題。假設： `let a = A()`，這時執行： `a.changeX()`

這個物件方法就開始逐行執行：&#x20;

* `doItLater { self.x = 100 }`&#x20;
* `doItNow { x = 200 }`

當執行到 `doItNow { x = 200 }` 時，因為物件 `a` 顯然還活著， 所以如果要改變 `a.x = 200` 當然不是問題。

但 `doItLater { self.x = 100 }` 這一行，單純只是把 `{ self.x = 100 }` 這項**工作存起來**而已，所以我們**如何保證**當「**以後**」真正執行時，`self` (也就是本例的 `a`) **物件還存活著**？

所以這裡就牽涉到一個 Swift 的絕活，它就是「**硬性規定**」你一定 要寫成 `{ self.x = 100 }` 的形式，而不是 `{ x = 100 }`， 來「**抓住**」(**capture**) `self` (也就是本例的 `a`) **物件**，保證讓這個物件**不會自動從記憶體中清除**，所以將來執行 `{ self.x = 100 }` 時才不會出問題。

### 物件抓得太緊 :question:  <a href="#reference-cycle" id="reference-cycle"></a>

至於說「**將物件抓住**」這個做法會不會產生 [strong reference cycle](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID56)，導致**物件無法清除**，進而產生**記憶體流失** (**memory leak**)，那就是程式設計師要考慮[如何解決](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID57)的另一個問題了。
