Taskで[weak self]を使わないで！

## 概要
- Task は循環参照が発生しないので、中で `self` を通さなくてもオブジェクトの自分自身のインスタンスにアクセスできてデフォルトで強参照でキャプチャされる
- 強参照で `self` がキャプチャされているならインスタンスがタスクが完了するまで保持されるが、タスクが完了すると正常に解除される
- タスク完了までインスタンスの寿命を伸ばしたくないときに `[weak self]` を使うと実装ミスが発生しやすいしタスク自体がリークしやすい
- タスクのキャンセルで対応しましょう

## そもそもどういうときに `[weak self]` を使いたいのか？

クロージャを書くときに弱参照と強参照を意識しないといけません。参照型オブジェクトは強参照で保持しているところがないときに破棄されるが、もしオブジェクト同士がお互いのインスタンスを保持したりとかクラス自体が直接的・間接的に自分のインスタンスを保持している場合は循環参照が発生してそのオブジェクトがリークしてしまいます。

```swift
class Foo {
    var counter = 0

    var action: (() -> Void)?

    init() {
        setup()
    }

    func setup() {
        action = {
            self.counter += 1
        }
    }

    // リークすると破棄されないから一生呼ばれない
    deinit {
        print("deinit Foo")
    }
}
```

この形では循環参照が発生し Foo のインスタンスは永久的に破棄されないからメモリリークが発生します。

```mermaid
graph LR
  Foo --> action --> self --> Foo
```

そこでキャプチャリストに `[weak self]` を指定すると弱参照に変わりメモリリークが解消されます。

```swift
    func setup() {
        action = { [weak self] in
            self?.counter += 1
        }
    }
```

```mermaid
graph LR
  Foo --> action -.->|弱参照| self --> Foo
```

Swift の言語方針として、こういうふうに循環参照が発生しうるクロージャでは `self` を明示的に指定しないといけなく省略ができないようになっています。逆にいえば、**循環参照が発生しないクロージャは `self` の指定を省略してもいいようになっています。**

## Task はどう違うのか？

Taskを使うとすぐに発見するのは、参照型オブジェクトで使ったときでも明示的の `self` が省略できてキャプチャリストでも指定しなくてもいいようになっています。

なぜそうなっているかというと、たとえそのタスクのインスタンスをオブジェクトで保持しても**Task 自体では循環参照が発生しない**からです。Swift の言語方針に沿って `self` 省略の条件がクリアされているから特別に[`@_implicitSelfCapture`  という隠し属性](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implicitselfcapture)が付けられています。

```swift
class Bar {
    var counter = 0

    var task: Task<Void, Never>?

    init() {
        setup()
    }

    func setup() {
        task = Task {
            counter += 1 // self はなくてもいい
        }
    }

    // 循環参照が発生しないからちゃんと呼ばれる
    deinit {
        print("deinit Foo")
    }
}
```


## でも、こういうときに必要なんじゃ？

ここで落とし穴が一つあります。Task の中で自分自身へのアクセスがあると、そのインスタンスの寿命がタスクが完了するまで延長されます。

例えば、タスクが実行されている時間は10秒だった場合は、10秒後にインスタンスが破棄されます。

```swift
    func setup() {
        task = Task {
            try? await Task.sleep(nanoseconds: 10 * 1_000_000_000) // 10秒待つ
            counter += 1
        }
    }
```

さらにいうと、自分で完了しない非同期処理があると永久的に寿命が延ばされ実質ではオブジェクトがリークします。

```swift
    func setup() {
        task = Task {
            let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values // Combine の Publisher から AsyncSequence を作成
            for await _ in dates  {
                counter += 1
            }
        }
    }
```

そこで今まで通りに completionHandler の実装経験にも基づいてクロージャのキャプチャリストに`[weak self]` を設定する形で対応したとします。

```swift
    func setup() {
        task = Task { [weak self] in
            let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values
            for await _ in dates  {
                self?.counter += 1
            }
        }
    }
```

よし、これでリークが解消された！と思いきや果たしてこの方針で対応すればいいのでしょうか。

#### 問題点1

`weak` を付けただけで実行されるタスクがキャンセルされるわけでもなく、リークする可能性があります。
```swift
    func setup() {
        task = Task { [weak self] in
            let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values
            for await date in dates  {
                print(date) // self が nil になった後でも `dates` は生き続けるからこのタスクは永久的に走る
                self?.counter += 1
            }
        }
    }
```

#### 問題点2

よくある `guard` でオプショナルをアンラップして使いやすくした場合、その場所によって対処策となりません。
```swift
    func setup() {
        task = Task { [weak self] in
            // guard let self else { return } // ❌ ここで置くと dates を await する前にアンラップされてオブジェクトがリークしてしまう
            let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values
            for await date in dates  {
                print(date)
                guard let self else { return } // ⚠️ ここなら OK。早期リターンがあることで、問題点1も解消される
                self.counter += 1
            }
        }
    }
```

ちゃんと場所も意識して `guard` をつけると対応できなくはないが、実装・レビューで常にその観点で見ないといけないのは割としんどく運用コストが高いように見えます。

#### 問題点3

キャプチャリストで `[weak self]` を指定したとしても、`self` を通さない自分自身のインスタンスへのアクセスは引き続き可能でコンパイルエラーになりません。

```swift
        task = Task { [weak self] in
            let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values
            for await date in dates  {
                print(date)
                guard let self else { return }
                self.counter += 1
            }

            counter += 1 // ‼️ self を付けなくてもアクセスできる。せっかくの weak self / guard でリーク回避したコードは台無しになる。
        }
```

これも実装・レビューの負担がかかる要因となります。


## じゃ、どう対応すればいい？

積極的にタスクキャンセルの仕組みを使いましょう。

上記のサンプルコードでは `task` を保持しているからそれに対して `.cancel()` を呼び出せばいいです。

```swift
Bar().task?.cancel()
```

```swift
    func setup() {
        task = Task {
            let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values
            for await date in dates  {
                print(date)
                guard !Task.isCancelled else { return } // 必須ではないが、キャンセルしたら以下の処理を実行させたくないなら付けてもいい
                counter += 1
            }
        }
    }
```

当然、オブジェクトの寿命が長いのは本来の問題だからオブジェクト自体の `deinit` からは呼び出しできなくどうしても外部からの制御が必要になってきます。実際にどう制御するかは、使う場面によって違ってきますが iOS 開発でよくありそうなのは画面のライフサイクルに紐づく形です。例えば VC の `viewDidLoad` で処理を開始して `deinit` でキャンセルするみたいな実装を想定しています。

```swift
extension Task: Cancellable { } // 標準でも準拠してもおかしくないが、していないので手動でする

class Baz {
    var counter = 0

    private var tasks: [any Cancellable] = []

    func setUp() {
        tasks.append(
            Task {
                let dates = Timer.publish(every: 1, on: .main, in: .common).autoconnect().values
                for await date in dates  {
                    print(date)
                    guard !Task.isCancelled else { return }
                    counter += 1
                }
            }
        )
    }

    func tearDown() {
        tasks.forEach { $0.cancel() }
    }

    deinit {
        print("deinit Baz")
    }
}

class VC: UIViewController {
    let baz = Baz()

    override func viewDidLoad() {
        super.viewDidLoad()
        baz.setUp()
    }

    deinit {
        print("deinit VC")
        baz.tearDown()
    }
}
```