【物語で学ぶ Swift Concurrency】CommonProblems.mdを徹底解説

## 放課後のプログラミング教室
### 1. 導入
**ユウタ：**「先生！ Swiftで同時実行（Concurrency）の学習をしてるんですが、コンパイラがいろいろ文句を言ってきて大変なんです。[『CommonProblems.md』](https://github.com/apple/swift-migration-guide/blob/main/Guide.docc/CommonProblems.md)という資料を見つけたんですけど、これ、どんなことが書かれているんでしょう？」


**ミズ・スウィフト（先生）：**「いい質問ね。実はこの[『CommonProblems.md』](https://github.com/apple/swift-migration-guide/blob/main/Guide.docc/CommonProblems.md)には、Swiftの並行処理を使ううえでよく遭遇するコンパイラエラーと、その原因と対処法が詳しく書かれているの。ちょうどいいから、アキラと一緒に、この資料を元に勉強してみましょうか。」

**アキラ：**「わあ、それは助かる！ どんな内容なのか、早速教えてください！」

**ミズ・スウィフト**：「わかったわ。まずは資料の冒頭を見てみましょう。こう書かれているの。」

>*CommonProblems.md より抜粋*
>
># Common Compiler Errors
>Identify, understand, and address common problems you can encounter while working with Swift concurrency.
>
>The data isolation guarantees made by the compiler affect all Swift code. This means complete concurrency checking can surface latent issues, even in Swift 5 code that doesn't use any concurrency language features directly. With the Swift 6 language mode enabled, some of these potential issues can also become errors.
>
>After enabling complete checking, many projects can contain a large number of warnings and errors. Don't get overwhelmed! Most of these can be tracked down to a much smaller set of root causes. And these causes, frequently, are a result of common patterns which aren't just easy to fix, but can also be very instructive while learning about Swift's concurrency system.


**ミズ・スウィフト：**「要するに、『Swift 6』でコンパイラのチェックが厳しくなって、これまで表に出なかった問題がエラーとして顕在化するかもしれない。でも心配しないで、よくある原因は意外と共通パターンがあるって話ね。」

**アキラ：**「確かに、前のバージョンのSwiftでは普通に動いていたコードが、急にエラーになったりするんですよね。」

**ユウタ：**「はい。じゃあ一つ一つ見ていきたいです！」

## 2. Unsafe Global and Static Variables
**ミズ・スウィフト：**「まずは『Unsafe Global and Static Variables』というセクション。グローバルな変数やstatic変数を乱用するとデータ競合が起こりやすいってことが書いてあるのよ。」

>*CommonProblems.md より抜粋*
>## Unsafe Global and Static Variables
>Global state, including static variables, are accessible from anywhere in a program. This visibility makes them particularly susceptible to concurrent access. Before data-race safety, global variable patterns relied on programmers carefully accessing global state in ways that avoided data-races without any help from the compiler.
>
>>Experiment: These code examples are available in package form. Try them out yourself in Globals.swift.



**ユウタ：**「なるほど、グローバル変数は色んなところから同時に触れちゃいますからね。」

**ミズ・スウィフト：**「そうそう。ここからさらに詳しい例が出ているわ。例えばこんなコードよ。」
```swift
var supportedStyleCount = 42
```
**ミズ・スウィフト：**「これをSwift 6モードでコンパイルすると、こんなエラーが出るの。」
```text
1 | var supportedStyleCount = 42
  |              |- error: global variable 'supportedStyleCount' is not concurrency-safe because it is non-isolated global shared mutable state
  |              |- note: convert 'supportedStyleCount' to a 'let' constant to make the shared state immutable
  |              |- note: restrict 'supportedStyleCount' to the main actor if it will only be accessed from the main thread
  |              |- note: unsafely mark 'supportedStyleCount' as concurrency-safe if all accesses are protected by an external synchronization mechanism
2 |
```

**アキラ：**「コンパイラがかなり丁寧にヒントをくれていますね。`let`にするといいとか、`@MainActor`にするといいとか。」

**ミズ・スウィフト：**「そうなの。そして問題の例として、異なる隔離ドメイン（例えばメインアクターと別のスレッド）が同時に書き換えると、データ競合が起こり得るわ。」

```swift
@MainActor
func printSupportedStyles() {
    print("Supported styles: ", supportedStyleCount)
}

func addNewStyle() {
    let style = Style()

    supportedStyleCount += 1

    storeStyle(style)
}
```
**ユウタ：**「メインアクターで実行される関数と、そうでない関数が同じ変数を触ると危険ってことですね。」

**ミズ・スウィフト：**「正解。対策の一例としては、変数に`@MainActor`を付けて、同じアクター上だけで扱うようにするか、あるいは値を変えないなら`let`にしてしまうの。それぞれこう書いてあるわ。」
```swift
@MainActor
var supportedStyleCount = 42
```
または
```swift
let supportedStyleCount = 42
```

**ユウタ：**「なるほど、変更しないなら`let`。変更するけどメインスレッドでしか使わないなら`@MainActor`。それでも無理なら？」

**ミズ・スウィフト：**「もし、ロック（排他制御）などでちゃんと保護してますよ、という場合には、`nonisolated(unsafe)`を使ってコンパイラチェックを無効化できるわ。でも本当に安全な場合のみね。」

```swift
/// This value is only ever accessed while holding `styleLock`.
nonisolated(unsafe) var supportedStyleCount = 42
```
**アキラ：**「これは『自分で責任を持つからチェックしないで！』という感じですね。乱用は危険そう。」

## 3. Non-Sendable Types
**ミズ・スウィフト：**「次に、ここで話されているのは、グローバルに公開されているのが`Sendable`じゃない型の場合よ。」

>**CommonProblems.md より抜粋**
>## Non-Sendable Types
>In the above examples, the variable is an `Int`, a value type that is inherently `Sendable`. Global reference types present an additional challenge, because they are typically not `Sendable`.
>```swift
>class WindowStyler {
>    var background: ColorComponents
>
>    static let defaultStyler = WindowStyler()
>}
>```
>The problem with this `static let` declaration is not related to the mutability of the variable. The issue is `WindowStyler` is a non-`Sendable` type, making its internal state unsafe to share across isolation domains.

**ユウタ：**「つまり、クラスは普通に非同期対応でない限り`Sendable`じゃないから、グローバルに置くと危ないってことですね。」

**ミズ・スウィフト：**「その通り。例えば以下みたいに使われた場合、データ競合が起こるかもしれないのよ。」
```swift
func resetDefaultStyle() {
    WindowStyler.defaultStyler.background = ColorComponents(red: 1.0, green: 1.0, blue: 1.0)
}

@MainActor
class StyleStore {
    var stylers: [WindowStyler]

    func hasDefaultBackground() -> Bool {
        stylers.contains { $0.background == WindowStyler.defaultStyler.background }
    }
}
```

**アキラ：**「同時アクセスが怖いってことですね。」

**ミズ・スウィフト：**「そう。解決策としては、`WindowStyler.defaultStyler`をグローバルアクターに隔離するか、`WindowStyler`自体を`Sendable`にするか、いろいろあるわ。」

## 4. Protocol Conformance Isolation Mismatch
**ミズ・スウィフト：**「次に、プロトコルと実装側で隔離の指定が食い違う場合の話ね。」

>*CommonProblems.md より抜粋*
>## Protocol Conformance Isolation Mismatch
>A protocol defines requirements that a conforming type must satisfy, including static isolation. This can result in isolation mismatches between a protocol's declaration and conforming types.
>
>There are many possible solutions to this class of problem, but they often involve trade-offs. Choosing an appropriate approach first requires understanding why there is a mismatch in the first place.
>
>>Experiment: These code examples are available in package form. Try them out yourself in [ConformanceMismatches.swift][ConformanceMismatches].

**ユウタ：**「プロトコルに`@MainActor`をつけるか、実装側に合わせるか、とかの話ですかね？」

**ミズ・スウィフト：**「そうそう。例えばプロトコルが isolation を指定していないと、それは『非隔離』扱いになるの。なのに実装側が`@MainActor`を付けていると、エラーになるわ。」
```swift
protocol Styler {
    func applyStyle()
}

@MainActor
class WindowStyler: Styler {
    func applyStyle() {
        // access main-actor-isolated state
    }
}
```
```text
 7 |     func applyStyle() {
   |          |- error: main actor-isolated instance method 'applyStyle()' cannot be used to satisfy nonisolated protocol requirement
```
**ミズ・スウィフト：**「プロトコルに`@MainActor`を付ければいい場合もあるし、非同期に変えて `func applyStyle() async` にするなど、対処はいろいろあるわね。例えば、プロトコル全体をメインアクターにするのか、メソッド単位にするのか、とか。」
```swift
// entire protocol
@MainActor
protocol Styler {
    func applyStyle()
}

// or per-requirement
protocol Styler {
    @MainActor
    func applyStyle()
}
```
**アキラ：**「どの方法がベストかは、使い方次第って感じですね。」

**ミズ・スウィフト：**「そうね。あと、既存のコードをすぐに変えられない場合は`@preconcurrency`とかで一時的に診断を緩和する方法もあるわ。」

## 5. Crossing Isolation Boundaries
**ユウタ：**「先生、関数間で引数をやりとりするときに、『`Sendable`じゃない！』って怒られることもあるんですが、これってどう対応するんですか？」

**ミズ・スウィフト：**「それが、資料の『Crossing Isolation Boundaries』に書いてあるわ。」

>*CommonProblems.md より抜粋*
>## Crossing Isolation Boundaries
>The compiler will only permit a value to move from one isolation domain to another when it can prove it will not introduce data races. Attempting to use values that do not satisfy this requirement in contexts that can cross isolation boundaries is a very common problem.

**ミズ・スウィフト：**「ポイントは、あるアクターやスレッドから別のアクターやスレッドにデータを送るとき、そのデータが`Sendable`かどうかをコンパイラがチェックしているってこと。もし非`Sendable`なら怒られるの。」

**ユウタ：**「`@MainActor`な関数に非`Sendable`な引数を渡すと怒られました……」

**ミズ・スウィフト：**「例えばこういう例があるわ。」
```swift
public struct ColorComponents {
    public let red: Float
    public let green: Float
    public let blue: Float
}

@MainActor
func applyBackground(_ color: ColorComponents) {
}

func updateStyle(backgroundColor: ColorComponents) async {
    await applyBackground(backgroundColor)
}
```
```text
  8 |     await applyBackground(backgroundColor)
    |           |- error: sending 'backgroundColor' risks causing data races
```
**アキラ：**「`ColorComponents`が`public`だから、暗黙の`Sendable`にならない、ってわけですね？」

**ミズ・スウィフト：**「そうなの。だから、ちゃんと `public struct ColorComponents: Sendable { ... }` って明示する必要があるわ。もちろん、それをAPI契約として維持しなきゃいけなくなるから注意が必要だけどね。」

## 6. Latent Isolation
**ユウタ：**「非`Sendable`を無理に`Sendable`にせずに済む方法もあるんですか？」

**ミズ・スウィフト：**「そう！ 実は、関数自体を`@MainActor`にするとか、そもそも引数を渡さなくて済むように設計を変えるとか、ほかにも手はあるのよ。」
```swift
@MainActor
func applyBackground(_ color: ColorComponents) {
}

func updateStyle(backgroundColor: ColorComponents) async {
    await applyBackground(backgroundColor)
}
```
---
```swift
@MainActor
func updateStyle(backgroundColor: ColorComponents) async {
    applyBackground(backgroundColor)
}
```
**ミズ・スウィフト：**「こうすると、引数の受け取り先も呼び出し元も、どちらも`MainActor`だから、隔離境界を越えずに済むわけ。」

**アキラ：**「なるほど。非同期をなくせる場合もありそうですね。」

**ミズ・スウィフト：**「そうそう。」

## 7. Computed Value / Sending Argument
**ミズ・スウィフト：**「ほかに、`@Sendable () -> T` なクロージャで値を作るだけにするとか、`sending`キーワードを使って引数として安全に渡すとか、細かいテクニックもあるわ。」
```swift
func updateStyle(backgroundColorProvider: @Sendable () -> ColorComponents) async {
    await applyBackground(using: backgroundColorProvider)
}
```
---
```swift
func updateStyle(backgroundColor: sending ColorComponents) async {
    await applyBackground(backgroundColor)
}
```
**ユウタ：**「あ、`sending`引数！ Swift 6で増えた新しい書き方ですよね？」

**ミズ・スウィフト：**「そうなの。こうして型に直接`Sendable`を付けずに、安全にデータを渡す工夫ができるわ。」

## 8. Sendable Conformance
**ミズ・スウィフト：**「次は『Sendable』への準拠をどうやって行うか、4つの方法がまとめられているから、一気に見てみましょう。」

>*CommonProblems.md より抜粋*
>
>When encountering problems related to crossing isolation domains, a very natural reaction is to just try to add a conformance to `Sendable`. You can make a type `Sendable` in four ways.
>##### 1. Global Isolation
>```swift
>@MainActor
>public struct ColorComponents {
>    // ...
>}
>```
>By isolating this type to the MainActor, any accesses from other isolation domains must be done asynchronously.
>##### 2. Actors
>```swift
>actor Style {
>    private var background: ColorComponents
>}
>```
>Actors have an implicit Sendable conformance because their properties are protected by actor isolation.
>##### 3. Manual Synchronization
>```swift
>class Style: @unchecked Sendable {
>    private var background: ColorComponents
>    private let queue: DispatchQueue
>}
>```
>##### 4. Retroactive Sendable Conformance
>```swift
>extension ColorComponents: @retroactive @unchecked Sendable {
>}
>```
>...
>##### Sendable Reference Types
>```swift
>final class Style: Sendable {
>    private let background: ColorComponents
>}
>```
**ミズ・スウィフト：**「要点をまとめると、(1)グローバルアクターにする、(2)アクターそのものにする、(3)手動で同期をとって`@unchecked Sendable`にする、(4)レトロアクティブに適用する、の4つって感じね。」

**アキラ：**「それと、`final`クラスかつ継承していないなど、いろいろ条件を満たせば、参照型もチェック付き`Sendable`にできるんですね。」

**ミズ・スウィフト：**「そう。あとは型を複合的に組み合わせて、部分的に`@MainActor`や`nonisolated(unsafe)`を使う、なんてことも紹介されてるわ。」
```swift
final class Style: Sendable {
    private nonisolated(unsafe) var background: ColorComponents
    private let queue: DispatchQueue

    @MainActor
    private var foreground: ColorComponents
}
```

## 9. Non-Isolated Initialization
**ユウタ：**「アクターにしたら、イニシャライザってどうなるんでしょう？ 普通に初期化すると『非isolatedなコンテキストだ！』って怒られたりしますよね？」

**ミズ・スウィフト：**「そこもちゃんと資料に書いてあるわ。」

>*CommonProblems.md より抜粋*
>## Non-Isolated Initialization
>Actor-isolated types can present a problem when they are initialized in a non-isolated context.
>
>Here the non-isolated `Stylers` type is making a call to a `MainActor`-isolated initializer.
>```swift
>@MainActor
>class WindowStyler {
>    init() {
>    }
>}
>
>struct Stylers {
>    static let window = WindowStyler()
>}
>```
>This code results in the following error:
>```text
> 9 |     static let window = WindowStyler()
>   |                `- error: main actor-isolated default value in a nonisolated context
>```
>Globally-isolated types sometimes don't actually need to reference any global actor state in their initializers. By making the `init` method `nonisolated`, it is free to be called from any isolation domain.

```swift
@MainActor
class WindowStyler {
    private var viewStyler = ViewStyler()
    private var primaryStyleName: String

    nonisolated init(name: String) {
        self.primaryStyleName = name
        // type is fully-initialized here
    }
}
```
**ユウタ：**「なるほど。イニシャライザだけ`nonisolated`にすれば、他のプロパティは`@MainActor`のままでもOKなんですね。」

**ミズ・スウィフト：**「そういうこと。初期化のときは特別だから、うまく使い分けないといけないのよ。」

## 10. Non-Isolated Deinitialization
**アキラ：**「あ、そういえばデイニシャライザ`deinit`も、アクターなのに並行アクセスの問題があるとか……」

**ミズ・スウィフト：**「そう。`deinit`は常に非isolatedなの。だから中でアクター隔離されたメソッド呼ぶとエラーになるわ。」

>*CommonProblems.md より抜粋*
>## Non-Isolated Deinitialization
>Even if a type has actor isolation, deinitializers are always non-isolated.
>```swift
>actor BackgroundStyler {
>    // another actor-isolated type
>    private let store = StyleStore()
>
>    deinit {
>        // this is non-isolated
>        store.stopNotifications()
>    }
>}
>```
>This code produces the error:
>```text
>error: call to actor-isolated instance method 'stopNotifications()' in a synchronous nonisolated context
>```
>Often, the work being done within the deinit does not need to be synchronous. A solution is to use an unstructured Task to first capture and then operate on the isolated values.
>```swift
>actor BackgroundStyler {
>    private let store = StyleStore()
>
>    deinit {
>        Task { [store] in
>            await store.stopNotifications()
>        }
>    }
>}
>```
>>Important: Never extend the life-time of `self` from within `deinit`. Doing so will crash at runtime.

**ミズ・スウィフト：**「`deinit`の中では`self`をキャプチャしないようにして、必要な処理はタスクの中でやる、っていうのがポイントね。」

**アキラ：**「気をつけないとですね。デイニシャライザは終了処理だからといって、自由に呼び出せるわけじゃないんだ……」

## まとめ
**ユウタ：**「わあ、長かったですけど、これが[『CommonProblems.md』](https://github.com/apple/swift-migration-guide/blob/main/Guide.docc/CommonProblems.md)の全部なんですね。いろんなケースがあって、すごく勉強になりました！」

**ミズ・スウィフト：**「そうでしょ？ Swiftの同時実行では、データ競合を防ぐための仕組みとしてアクターや`Sendable`が導入されているから、コンパイラがいっぱい警告してくれるの。でも、それをどう対処するか、パターンを覚えれば怖くないわ。」

**アキラ：**「グローバル変数、クラスの`Sendable`、プロトコルの隔離、境界を越えた引数の受け渡し、イニシャライザとデイニシャライザ……一通り押さえておけば安心ですね！」

**ミズ・スウィフト：**「ええ。Swift 6の新しいルールに最初は戸惑うかもしれないけど、一つひとつは理にかなっているの。エラーが出ても、慌てずこうした資料を参考にして、正しく修正していきましょう。」

**ユウタ：**「はい、先生、ありがとうございました！ これでデータ競合に悩まされずに済みそうです！」

**アキラ：**「僕も、さらにSwiftのコードを書くのが楽しみになりました！」

---

以上が、[『CommonProblems.md』](https://github.com/apple/swift-migration-guide/blob/main/Guide.docc/CommonProblems.md)に書かれている内容のすべて（文章とコード例）を、高校生向けに物語形式でまとめたものです。各種サンプルコードとともに、エラー例や対処法が一通り整理されていますので、ぜひ参考にしてみてください。