【Swift】Concurrencyの基本

SwiftのConcurrencyを勉強していると、`async/await`、`Task`、`MainActor`、`actor`、`Sendable`...等々、たくさんの単語が出てきます。
ここでは、そうしたConcurrencyの基本について解説していきます。
## Concurrencyの概要
Swiftにおける処理は基本、コードが上から順に実行されていきます。こうした同期的処理は、**main thread**という単一のスレッドで実行されていきます。
しかし、APIへのデータフェッチなど、完了までに時間がかかる処理があると実行が止まってしまうため、UXとして非常に悪くなってしまいます。
そのため、複数のスレッドで同時に実行し、時間がかかる処理はmain thread以外で処理することでこうした問題を解決する為に開発されたのが、今回紹介する**Swift Concurrency**です。
## Concurrencyの基本
### [Task](https://developer.apple.com/documentation/swift/task)
非同期で実行する処理のまとまりです。Task内で定義された処理はいずれかのthread内で実行され、`Task`内の処理が完了するか、または`cancel()`が呼ばれるまで継続します。
以下のように、時間がかかる処理であってもmain threadで行われる処理の遅延等の影響は無くなります。
```swift
func process1() {
    Task {
        var count = 0
        print("process1 started")
        for _ in 0..<10000 {
            count += 1
        }
        print("process1 ended")
    }
}

print("main thread print start")
process1()
print("main thread print end")
// main thread print start
// main thread print end
// process1 started
// process1 ended
```
また、`cancel()`が呼ばれた際に処理を止めたい場合などは、`Task.checkCancellation()`を呼ぶことによって、`cancel()`が呼ばれた際に該当箇所でエラーが吐き出され、処理が止まるようになります。
### async/await
しかし、`Task`中の処理は並行処理であり、`Task`内に書かれた順番に関わらず同時に実行されるため、以下のような場合に順番がバラバラになります。
```swift
func process1() {
    Task {
        var count = 0
        print("process1 started")
        for _ in 0..<10000 {
            count += 1
        }
        print("process1 ended")
    }
}

func process2() {
    Task {
        var count = 0
        print("process2 started")
        for _ in 0..<1000 {
            count += 1
        }
        print("process2 ended")
    }
}

print("main thread print start")
process1()
process2()
print("main thread print end")
// main thread print start
// main thread print end
// process1 started
// process2 started
// process2 ended
// process1 ended
```
このように、非同期処理の中でも「前の処理が終わるまで次の処理を実行したくない」場合があると思います。これを解決するのが`async/await`です。
以下のように、関数を`async`にし、また呼び出し部分全体を`Task`で囲ったのちに関数に`await`を付与すると、以下のように上から順に処理が開始、終了されるようになります。
```swift
Task {
    print("main thread print start")
    await process1()
    await process2()
    print("main thread print end")
}

// main thread print start
// process1 started
// process1 ended
// process2 started
// process2 ended
// main thread print end
```
※実際、この関数は`async`、`Task`ともにつけなくてもこの順番で実行されます。`for`ループが同期処理である為です。ここでは説明のためにあえて`async`を付与しています。

**全ての`async/await`の処理は必ず、何らかの`Task`の中で実行されます。**
### Async Property
上記は関数の話でしたが、例えばプロパティを非同期で取得したい場合はどのようにすれば良いのでしょうか。
実は、以下のようにgetterに`async`をつけることによって、`async`関数同様に値を取得できるようになります。
```swift
func getDataAsync() async throws -> String {
    try await Task.sleep(for: .seconds(1))
    return "Data"
}

struct DataPool {
    var data: String {
        get async throws {
            return try await getDataAsync()
        }
    }
}

let dataPool = DataPool()
print(try await dataPool.data)
// Data
```
## Actor
### 概要
しかし、Concurrencyでは並行で処理を実行するというその性質上、いくつかのリスクも抱えています。
例えば、二箇所で同じ値を同時に書き換えした場合、予期せぬ変化、状態が生じてしまうリスクを孕んでいます。
そのような複数のスレッドから同時に変更が加わってしまう、いわゆるデータ競合状態を防ぐために登場したのが、今回紹介するactorになります。
### 具体例
例えば、銀行にお金を預けたり引き出したりする場合を考えます。
```swift
class ClassBank {
    private var current = 0

    func add(_ amount: Int) {
        current += amount
    }

    func remove(_ amount: Int) {
        if current > 0 {
            current -= amount
        }
    }

    var currentSum: Int {
        return current
    }
}

DispatchQueue.global(qos: .default).async {
    for _ in 0..<100 {
        ABank.add(100)
    }
}
DispatchQueue.global(qos: .default).async {
    for _ in 0..<100 {
        ABank.remove(100)
    }
}
Task {
    try await Task.sleep(for: .seconds(1))
    print(ABank.currentSum)
    // 結果はばらつく
}
```
上記のコードの場合、`100`の金額に対して100回`add`を呼び100回`remove`を呼んでいるので合計金額は`0`になるはずです。
しかし、変更を加える順番は保証されておらず、`currentSum`の結果がばらつくことから一定の割合で`remove`処理が失敗しているのがわかります。

そこで以下のように書くことで、`actor`がデータ競合を解決してくれます。
```swift
actor ActorBank {
    private var current = 0

    func add(_ amount: Int) {
        current += amount
    }

    func remove(_ amount: Int) {
        if current > 0 {
            current -= amount
        }
    }

    var currentSum: Int {
        return current
    }
}
let BBank = ActorBank()
Task {
    for _ in 0..<100 {
        await BBank.add(100)
        await BBank.remove(100)
    }
}
Task {
    try await Task.sleep(for: .seconds(1))
    print(await BBank.currentSum) // 0
}
```
`actor`のメソッドは`await`をつけることでのみ呼ぶことができるので、確実に`add`が呼ばれた後に`remove`が呼ばれる、ということが保証されます。
そのため、結果は必ず`0`になります。
### actor isolation
`actor`のプロパティは一箇所からのアクセスしか受け付けておらず、プロパティへアクセスする際にも`await`をつける必要があります。
このように`actor`のプロパティへのアクセスが一箇所からに制限されていることを**actor isolation**と呼びます。
しかし、`await`する必要がないプロパティ(例えば、定数や定数と連動するcomputed propertyなど、変化しないことが保証されているもの)もあります。
そうしたものには`nonisolated`修飾子をつけてあげることで、`await`がなくともそのプロパティにアクセスできるようになります。
```swift
let BBank = ActorBank(name: "BBank")
actor ActorBank {
    private var current = 0
    let name: String

    init(name: String) {
        self.name = name
    }

    func add(_ amount: Int) {
        current += amount
    }

    func remove(_ amount: Int) {
        if current > 0 {
            current -= amount
        }
    }

    var currentSum: Int {
        return current
    }

    nonisolated var bankName: String {
        return name
    }
}
print(BBank.bankName) // BBank
```
### [Sendable](https://developer.apple.com/documentation/swift/sendable)
そして、`actor`のように、数箇所からの変更が加わらないことが保証されている値が準拠するprotocolが存在します。それが`Sendable`です。
`Sendable`は以下のいずれかの条件に当てはまるものが準拠できます。
1. 値型であること
1. 参照型だが、可変変数がないこと
1. 参照型だが、内部の変数のアクセスコントロールが適切になされていること
1. `@Sendable`がついた関数やクロージャ

また、`struct`や`class`などの各場合において`Sendable`に準拠するための条件が上記公式ドキュメントに詳しく載っています。詳しくはそちらを参照ください。
Sendableに準拠していると、内部状態の変更が行えない、または`actor`のメソッドを通じてしか変更ができなくなります。
このプロトコルによって、さまざまな場所で非同期処理によって変数の変更を行っても競合や不整合が起こらず、安全に値を扱うことができるようになります。
## まとめ
- Swift Concurrencyは時間がかかる処理を複数のスレッドで同時に実行する手法
- `Task`は非同期で実行する処理のまとまりで、内部の処理は処理が完了するか、または`cancel()`が呼ばれるまで継続する。
- `async/await`により前の処理が終わった後に次の処理が始まることが保証される。これらの処理は必ず、`Task`内で呼ばれる。
  - プロパティも`async/await`で非同期取得することができる。
- `actor`はデータに関する競合を防ぐために存在するclassのようなもので、`async/await`でメソッドを呼び出すことで一箇所のみから内部状態を更新することが保証される。
- `Sendable`は一箇所のみから更新されることが保証されており、データ競合が起こらない値が準拠しているprotocol。`struct`や`class`などさまざまな場合によってその準拠条件が異なる。

### 最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。