【物語で学ぶ Swift Concurrency】最速バトンリレー！TaskGroupのnext()で見る並列処理の真髄

# 🏫 放課後のプログラミング部

## 登場人物
- **先生**：プログラミング部の指導教員
- **ユウタ**：高校2年生（生徒）
- **アヤカ**：高校2年生（生徒）、プログラミング経験あり

---

## シーン1： `group.next()` と `for await in` の違いを知る

放課後の部室。先生がホワイトボードを使って説明をはじめる。

**先生**「みんな、前に `withTaskGroup` を使って**並列処理**ができるのをやったよね。 `for await in group` を使うと、**子タスクの結果**を簡単に全部まとめて受け取れたけれど、今日はもう一つの取り方を紹介するよ。それが `group.next()` なんだ。」

**ユウタ**「えっ、`group.next()` って何ですか？  `for await in group` とどう違うんです？」

**先生**「`for await in group` は、グループ内で**完了した子タスク全て**を順番に受け取る手段だったよね。でも `group.next()` は**1回の呼び出しで1つずつ**、先に終わったタスクから**順番に**取り出すことができるんだ。」

**アヤカ**「なるほど！ `for await in group` だと、全部の子タスク結果を同じ構文の中で処理する形だったけど、`group.next()` なら**結果を個別**に取り出すことができるってことですね。」

---

## シーン2： `group.next()` の使い方

先生はホワイトボードにサンプルコードを書き始める。
```swift
// TaskGroupView.swift
// ふたつの子タスクのうち最初に終わったタスク結果を得る
guard let firstResult = await group.next() else {
    group.cancelAll()
    return
}
switch firstResult {
case .articles(let a):
    articles = a
case .friends(let f):
    friends = f
}

// 2番目に終わった結果を得る
guard let secondResult = await group.next() else {
    group.cancelAll()
    return
}
switch secondResult {
case .articles(let a):
    articles = a
case .friends(let f):
    friends = f
}
```

**先生**「このコードでは、**2つの子タスク**を作っておいて、`group.next()` で**先に終わったほう**のタスク結果を最初に取り出しているよ。もし `group.next()` が `nil` を返したら、つまりもう結果が取れない状態だから、`group.cancelAll()` で残りのタスクをキャンセルしてリターンさせてる。」

**ユウタ**「`switch`文で `articles` とか `friends` っていうのを見分けて、どっちの結果か判定してるんですね。」

**先生**「そう。これは**列挙型**で返ってきていて、どちらのパターンなのかを `switch` で振り分けている形だ。こうして一つ目と二つ目の結果を順々に取り出して処理するんだよ。」

---

## シーン3： `group.next()` のメリットとキャンセル

先生がさらにコードを書き足す。
```swift
guard let firstResult = await group.next() else {
    group.cancelAll()
    return
}

// ...初めの結果が想定外だったらキャンセルする...
group.cancelAll()
```

**先生**「実は `group.next()` を使うと、こうやって**最初に取得した結果次第**で残りの子タスクをキャンセルする、なんて処理もできるんだ。`for await in group` だとまとめて受け取るから、こういった**柔軟な制御**が少しやりにくいんだよね。」

**ユウタ**「なるほど…例えば、APIのレスポンスが『エラーだったらもう他のタスクも全部止める』みたいなことが簡単にできる、ってことですね！」

**先生**「そういうこと。もし最初のタスクから受け取った情報が『もう十分だ』となったなら、他のタスクは**無駄**になるかもしれない。そんな時に `group.cancelAll()` が役立つわけだ。」

---

## シーン4： 動的な数の並列処理を実行する

**アヤカ**「先生、たとえば友達のIDをたくさん持ってる場合に、それぞれのアバター画像をまとめて取りに行きたいことがあるんですけど、どうすればいいですか？」

**先生**「その場合は**ループ**を使って `group.addTask` をどんどん追加すればOKだよ。ちょうどサンプルがあるから見てみよう。」

先生がホワイトボードにサンプルを示す。
```swift
func fetchFriendsAvators(ids: [String]) async -> [String: UIImage?] {
    return await withTaskGroup(of: (String, UIImage?).self) { group in
        for id in ids {
            group.addTask { [weak self] in
                return (id, await self?.fetchAvatorImage(id: id))
            }
        }
        var avators: [String: UIImage?] = [:]
        for await (id, image) in group {
            avators[id] = image
        }
        return avators
    }
}
```


**ユウタ**「えっ、こんな風に `for id in ids` の中で `group.addTask` を書いていいんですね！」

**先生**「そう。ここでは `ids` の数だけ**子タスク**を追加してるんだ。タスクの戻り値の型を `(String, UIImage?)` にしておけば、受け取り側でも `(id, image)` みたいにタプルで取り出せるよ。」

---

## シーン5： 理解を深めるまとめ

**先生**「今日は大きく分けて2つの話をしたよ。1つ目は `group.next()` を使って、**先に終わったタスクの結果**を1つずつ取り出せるってこと。状況によっては、結果を見てから `group.cancelAll()` することで無駄なタスクを止めるような**柔軟な制御**ができる。」

**ユウタ**「`for await in group` との違い、よく分かりました。あっちは全部終わるまでの結果を一気に受け取る感じで、`group.next()` は1個ずつ確認していくイメージですね。」

**先生**「そう。2つ目は**動的な数**の並列処理。配列の要素分だけ `group.addTask` すれば、同時にたくさんの処理を走らせられる。受け取りは `for await (id, image) in group` でタプルを受け取って辞書に格納する形。これで**効率的に一斉ダウンロード**ができるよ。」

---

## 最後に

- `group.next()`：子タスクを**一つずつ**先に終わった順に取り出す  
  - **メリット**：結果次第で `cancelAll()` などをして、柔軟にコントロールできる  
  - `for await in group` とは**異なる使い方**が可能

- `withTaskGroup` のループ利用：配列などの要素数に応じて**動的**に子タスクを追加  
  - ダウンロードやAPI呼び出しを**並行**で処理し、最後に結果をまとめて返せる

**ユウタ**「今日もよく分かりました！」

**先生**「これで並列処理の幅が広がるね！」