`Task.init` に渡すクロージャが暗黙的に `self` をキャプチャすることの背景と注意点

## 概要

`@escaping` なクロージャでは、クロージャの中で参照型の `self` のプロパティにアクセスするときなど明示的に `self` を書かなければいけません。これに対して、Swift Concurrency で使われる `Task.init` に渡すクロージャでは `@escaping` であるにも関わらず暗黙的に `self` をキャプチャするので `self` を書かなくてもいいという例外的な振る舞いをします。その背景と注意点についてまとめます。

この記事中での動作検証は Xcode 14 Beta 3 で行っています。

## `@escaping` なクロージャと `self`

本題に入る前に、まずは `@escaping` と `self` の関係について確認していきます。ある関数がクロージャを引数で受け取るとき、そのクロージャをその場ではなくあとで実行する可能性がある場合は引数に `@escaping` 属性をつける必要があります。具体的にあとで実行する可能性があるというのはどういうときかというと、

- 受け取ったクロージャをプロパティに保存するとき
- 受け取ったクロージャを非同期で実行するとき
- さらに別の関数の `@escaping` な引数に渡すとき

が挙げられます。

以下の例では、 

- `executeTwice` に渡す `operation` はその場ですぐ実行されるので `@escaping` が不要
- `saveOperationForLaterUse` に渡す `operation` はプロパティとして保持されるため `@escaping` が必要
- `executeAsync` に渡す `operation` は `DispatchQueue.main.async` の `@escaping` なクロージャの中で実行されるため `@escaping` が必要
    - ちなみに `DispatchQueue.main.async` のクロージャが `@escaping` になっているのは非同期で実行されるため

です。

```swift
final class Executor {
    private var operation: (() -> Void)?
    
    // その場で実行するので @escaping は不要
    func executeTwice(operation: () -> Void) {
        operation()
        operation()
    }
    
    // プロパティに保存するので @escaping が必要
    func saveOperationForLaterUse(operation: @escaping () -> Void) {
        self.operation = operation
    }

    // DispatchQueue.main.async の `@escaping` なクロージャの中で実行するので @escaping が必要
    func executeAsync(operation: @escaping () -> Void) {
        DispatchQueue.main.async {
            operation()
        }
    }
}
```

`@escaping` なクロージャの中で `self` のプロパティを参照する必要があり、かつその `self` が値型ではなく参照型の場合は明示的に `self` をつけてアクセスする必要があります。たとえば以下のように `saveOperationForLaterUse` に渡すクロージャの中では `number` とはアクセスできず `self.number` と書く必要があります。

```swift
final class ViewController: UIViewController {
    private let executor = Executor()
    private var number: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

		executor.executeTwice {
            // @escaping ではないので self が必要ない
            print(number)
        }
        executor.saveOperationForLaterUse {
            // @escaping なので self が必要
            print(self.number)
        }
    }
}
```

このような制約があるのは、開発者にクロージャが `self` をキャプチャしていることを意識させるためです。なぜクロージャが `@escaping` の場合だけ意識しないといけないかというと、 `@escaping` なクロージャが `self` をキャプチャすると循環参照が発生し、メモリリークを引き起こす可能性があるためです。

実際、上記の実装では `ViewController` が `Executor` を、 `Executor` が `operation` を、 `operation` が `self` を介して `ViewController` を参照するので循環参照になり、 `ViewController` が UI 上でポップされたとしてもメモリから解放されないという問題が起こります。これを避けるためには、以下のように `weak self` を使ってクロージャから `self` を強参照しないことにより循環参照を断ち切る必要があります。

```swift
        // ...
        executor.saveOperationForLaterUse { [weak self] in
            print(self?.number)
        }
        // ...
```

ここで `self.number` ではなく単純に `number` とアクセスできてしまうと、クロージャが `self` をキャプチャしている＝メモリリークを避けるために `weak self` が必要であること自体に気づきづらいので明示的に `self.number` と書く必要があるというわけです。

`@escaping` でないクロージャについては、その場で実行されるだけで他のオブジェクトから強参照されることがないので循環参照が発生しないことから `self` をつけなくてもコンパイルが通ります。

## `@_implicitSelfCapture` による暗黙的な `self` のキャプチャ

Swift Concurrency では、同期的な処理から非同期な関数を呼ぶために [`Task.init(priority:operation:)`](https://developer.apple.com/documentation/swift/task/init(priority:operation:)-5k89c) を使います。この `Task.init` に渡すクロージャである `operation` 引数は非同期に実行されるため `@escaping` 属性がついています。前項で説明したように普通の `@escaping` なクロージャの中では明示的に `self` を書く必要がありますが、実は `Task.init` に渡すクロージャのみ例外的に `self` を書かなくてもよいことになっています。これは、 `@escaping` でないクロージャと同様に暗黙的に `self` がキャプチャされるということを意味しています。

たとえば、以下のような書き方ができるということです。

```swift
final class ViewController: UIViewController {
    var number: Int = 0

    override func viewDidLoad() {
        Task {
            try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
            // @escaping なクロージャの中なのに
            // self.number と書かなくてもコンパイルエラーにならない！
            print(number)
        }
    }
}
```

前項で説明したように、 `@escaping` なクロージャで `self` を書かないといけない制限には、メモリリークを避けるために `self` のキャプチャを開発者に意識させるという理由がありました。それにも関わらず `Task.init` においてその制限があえて外されているのは、 `Task.init` が循環参照を作らないためです。

`Task.init` に渡されるクロージャは非同期に実行されるという理由で `@escaping` 属性がついていますが、クロージャがプロパティに保持された前項の `Executor.saveOperationForLaterUse` のような例とは異なり、すぐに実行されて実行完了後にはメモリから解放されます。つまり、上記の例のクロージャは３秒間だけ `self` を介して `ViewController` を参照し続けますが、実行が完了し次第クロージャが解放されて `ViewController` も参照されなくなるので `ViewController` が必要もないのに永続的にメモリ上に残ってしまうということは起こりません。循環参照が発生しない以上、開発者は `Task.init` に渡したクロージャが `self` を参照するかどうかを意識する必要がないため、記述の簡潔さのために `self` を書かずに `self` のプロパティにアクセスすることができるようになっています。

この暗黙的な `self` のキャプチャは、 `Task.init` に渡されるクロージャに [`@_implicitSelfCapture`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implicitselfcapture) をつけることで実現されています。

https://github.com/apple/swift/blob/dcea14716cb17b080dc374c831b5717d96800738/stdlib/public/Concurrency/Task.swift#L560-L565

アンダースコアで始まるアトリビュートは主に swift コンパイラや標準ライブラリでの利用が想定されているのでアプリケーションで使われることは推奨されていませんが、もちろん自分でも使うことができ、 以下の `executeAsync` の `operation` は `@_implicitSelfCapture` によって `self` を暗黙的にキャプチャするため、プロパティにアクセスするときに `self` を書かなくてもよくなります。

```swift
final class Executor {
    func executeAsync(@_implicitSelfCapture operation: @escaping () -> Void) {
        DispatchQueue.main.async {
            operation()
        }
    }
}

final class ViewController: UIViewController {
    private let executor = Executor()
    private var number: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

		executor.executeAsync {
            // self.number と書かなくてもコンパイルエラーにならない！
            print(number)
        }
    }
}
```

## 暗黙的な `self` のキャプチャの注意点

以上のように、基本的には `Task.init` のクロージャから `self` が参照されても問題なく、そのために `@_implicitSelfCapture` が使用されているのですが、注意した方がよいポイントもあります。

### `weak self` をつけても `self` を強参照してしまうことに気づきづらい

まず、 `Task.init` にあえて `weak self` をつけた場合でも `self` を強参照してしまう場合があり、それに気づきづらいことが挙げられます。前項の例ではクロージャが `ViewController` を参照し続けるのは３秒間だけなので問題ないと書きましたが、逆に言うと `ViewController` の `viewDidLoad` が呼ばれた後すぐに UI 上でポップされても３秒間は `ViewController` がメモリ上に保持された上で `print` が実行されることになります。もしこれが許容できないと言う場合、簡単な対処として以下のように `weak self` をつけることが考えられます。これにより、クロージャが `ViewController` のリファレンスカウントを増やさなくなるので UI 上でポップされると `ViewController` はすぐ解放されます。

```swift
final class ViewController: UIViewController {
    var number: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        Task { [weak self] in
            try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
            print(self?.number)
        }
    }
}
```

ただ、 `weak self` をつけた上でも相変わらず暗黙的に `self` がキャプチャされることに注意する必要があります。例えば以下のようにクロージャの中で別のプロパティである `otherNumber` に直接アクセスした場合、これは `self.otherNumber` のことを意味するので `self` を強参照することになり、 `ViewController` の寿命が３秒間伸びる元の振る舞いに戻ってしまいます。

```swift
final class ViewController: UIViewController {
    var number: Int = 0
    var otherNumber: Int = 42

    override func viewDidLoad() {
        super.viewDidLoad()
        Task { [weak self] in
            try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
            print(self?.number)

            // self を強参照してしまっている
            print(otherNumber)
        }
    }
}
```

`weak self` にしたからには `self?.otherNumber` とするべきなのですが、 `Task.init` では `self.otherNumber` ではなく単に `otherNumber` とアクセスできてしまうので `self` を参照していることに気付くのが難しくなっていると思います。このように `weak self` をしたにも関わらず意図せず `self` を強参照しないように注意しておく必要があります。

関連して、 `weak self` した場合によくクロージャの先頭で `guard let self = self else  { return }` の処理を挟むと思いますが、これを `Task.init` でやってしまうと `weak self` した意味がほとんど無くなってしまいます。

```swift
final class ViewController: UIViewController {
    var number: Int = 0
    var otherNumber: Int = 42

    override func viewDidLoad() {
        super.viewDidLoad()
        Task { [weak self] in
            guard let self = self else { return }
            try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
            print(self.number)
        }
    }
}
```

上のコードでは `Task.init` の先頭で `guard let` で強参照に戻すことにより `Task` が実行されてすぐクロージャが `self` を強参照することになってしまいます。これは、最初から `weak self` しない場合と実質的に同じ意味になり、実際上のコードでは `ViewController` が UI 上でポップされた後も３秒間メモリ上に残ってしまいます。

通常の `@escaping` クロージャの `weak self` は循環参照を避けるために行うので先頭で `guard let self` しても意味があるのですが、 `Task.init` のクロージャに `weak self` をつける目的はあくまで実行中に `self` を強参照しないことなので、実行の最初に `guard let self` すると意味がなくなってしまうということです。

`Task.init` で `weak self` したく、かつ `self` を強参照に戻す必要がある場合は、以下のようにその直前で強参照に戻すのがよいと思います。

```swift
final class ViewController: UIViewController {
    var number: Int = 0
    var otherNumber: Int = 42

    override func viewDidLoad() {
        super.viewDidLoad()
        Task { [weak self] in
            try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
            // 必要になる直前で強参照に戻す
            guard let self = self else { return }
            print(self.number)
        }
    }
}
```

### `Task.init` が実行され続ける場合はメモリリークにつながる

さらに重要な問題になり得るのが、 `Task.init` の中で無限ループが作られたとき、循環参照がないにも関わらず永続的なメモリリークが発生してしまう点です。ここまでの例では、 `self` の参照によって `ViewController` の寿命が伸びるのはあくまで `Task` が実行中のみで、その `Task` が３秒間で実行終了するため大きな問題はありませんでした。逆に言うと、 `Task` の実行が永久に終わらない場合は `ViewController` への参照が永久に続いてしまうため、循環参照がないにも関わらず `ViewController` がメモリリークします。

`Task` の実行が永久に終わらないということが実際に発生する場面として、 `Task.init` の中で終わらない `AsyncSequence` を `for await` で購読する場合があります。以下の `for await` は `ViewController` が UI 上でポップされても関係なく続くため、 `Task` は実行され続けます。その `Task` が `number` を介して `ViewController` を参照しているため、 `ViewController` がずっとメモリ上に残り続けてしまいます。

```swift
final class ViewController: UIViewController {
    var number: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            for await _ in NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).values {
                print(number)
            }
        }
    }
}
```

このメモリリークを防ぐための方法として、まずは以下のように `weak self` をつけることが思いつくかもしれません。

```swift
    // ...
    override func viewDidLoad() {
        super.viewDidLoad()
        Task { [weak self] in
            for await _ in NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).values {
                print(self?.number)
            }
        }
    }
    // ...
```

これにより、 UI 上でポップされると `ViewController` の強参照がなくなるので `ViewController` はメモリから解放されます。ただ、その場合でも `Task` は相変わらず実行され続けることに気をつけなければいけません。 `Task` も実行され続ければリソースを使ってしまいますし、実行され続ける `Task` が通知を受け取ると想定外に処理を実行してしまうことも問題です。画面上にない `ViewController` が処理を実行することで非常に見つけづらいバグの元になることも考えられるためです。上記の例では `print` しているだけなのでどうでものいいですが、例えば購読処理の中で API リクエストや DB へのアクセスをしている場合は大きな問題になることもあり得ます。 `ViewController` がメモリリークする場合はインスタンスが同時に複数存在する状況に容易になり得るので、それと同じ数の `Task` が同時に DB へのアクセスしてしまうとリソースの無駄というだけではなくクラッシュやデータの不整合を引き起こすかもしれません。

`ViewController` と `Task` の両方を適切にメモリから解放するためには以下のように適切なタイミングで `Task` をキャンセルするのがよいと思います。以下の実装だと、 `ViewController` が UI 上でポップされて `viewWillDisappear` が呼ばれると `Task` がキャンセルされるため実行が終了し、それにより `ViewController` への参照もなくなるのでメモリリークは発生しなくなります。

```swift
final class ViewController: UIViewController {
    var number: Int = 0
    var task: Task<Void, Never>?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        task = Task {
            for await _ in NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).values {
                print(number)
            }
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        task?.cancel()
    }
}
```

この変更の際に `Task.init` を呼ぶ場所を `viewDidLoad` から `viewWillAppear` に移動しています。これは `viewDidLoad` で購読処理を行うままだと、 `ViewController` が別の view controller を present → `viewWillDisappear` が呼ばれて `Task` がキャンセルされる → present していた別の view controller を dismiss して再度 `ViewControler` が表示される、という際に `Task` が実行されていない状態になってしまい、 `for await` 内の購読処理が実行されないためです。これを避けるためにキャンセルを実行する `viewWillDisappear` と対応するライフサイクルである `viewWillAppear` で `Task.init` を実行することにより、再度 `ViewController` が表示された際に `Task.init` が実行されるので画面に表示されている間は常に購読が走っている状態を作ることができると思います。

## まとめ

- `Task.init` に渡すクロージャは `@escaping` であるにも関わらず `@_implicitSelfCapture` によって暗黙的に `self` をキャプチャする
- このような仕様になっているのは `Task.init` が循環参照を作らないことから、開発者がクロージャからの `self` の参照を意識する必要がないため
- 注意点
    - `weak self` を使った上で意図せず `self` を強参照してしまうことがあるので気を付ける
    - `Task.init` の中で `AsyncSequence` を購読する場合などは循環参照と関係なくメモリリークが発生し得るので気を付ける

## 参考

- [Apple | init(priority:operation:)](https://developer.apple.com/documentation/swift/task/init(priority:operation:)-5k89c)
- [SE-0304 Structured concurrency - Implicit "self"](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md#implicit-self)
- [UnderscoredAttributes - @_implicitselfcapture](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implicitselfcapture)