Swift Concurrency まとめ

# WWDC

#### Meet async/await in Swift
https://developer.apple.com/videos/play/wwdc2021/10132

非同期処理をコールバックやデリゲートで書いていましたが、async/awaitキーワードを使って自然にかけるようになりました。

非同期の処理を行う関数、変数やイニシャライザの宣言部分にasyncをつけます。これを呼び出す時、awaitキーワードをつけて呼ぶだけです。エラー処理のあるような関数ではthrowsも同時につけることが多いと思いますが、`async throws`の順で、呼び出し時は`try await`の順でつけます。

```swift:hoge.swift
func someAsyncfunction() async throws String {
...
}

let result = try await someAsyncfunction()
```

awaitで他の関数呼び出している関数自体も、asyncキーワードをつけて宣言し直す必要があります。

async をつけると、そこで一旦処理がストップし、先に進まなくなります。しかしスレッドを占有はしないため、待っている間も他の処理（例えばユーザーのUI操作への対応など）は挟まることができます。asyncをつけた処理が終わると、一旦ストップしていた処理が再開し、次の業に進んでいくことになります。

- `await`は、次の行以降でその関数の結果が必要な時に使う
- `async let`は、次の行では直ちにその関数の結果は必要なく、もっと後になってからで良いという時に使う
- `await`でも`async let`でも、アプリの他の箇所で行われている非同期処理は止まらない

というのが区別のポイントです。

```swift:.hoge.swift
func someAsyncfunction() async throws String {
...
}

func anotherAsyncfunction() async throws String? {
   let result = try? await someAsyncfunction()
   return result
}
```

非同期処理の関数は、XCTestでテストするとき`expectation`を使ってわざわざ待ち受けするなど、面倒な書き方をしなくてはいけませんでしたが、これも必要なくなり、普通の関数とほとんど同じように書けるようになります。違うのはasyncをつけるだけです。
![截屏2021-07-01 20.09.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/31b25b74-b323-dd2f-8320-a42019115bab.png)

なんらかのクロージャの中でasyncを使おうとすると、そのクロージャ自体がawaitに対応していないためそのままだとうまく使えない場合があります。その場合、`async { }`クロージャで囲うと対応できます。

![截屏2021-07-01 20.07.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/e819e10b-218d-3595-4ba7-8b997354db12.png)

既存で、コールバックを使っている非同期処理関数を、`continuation`を使って、awaitの関数に置き換えることができます。
`continuation.resume()`を呼んだところで結果が返されますが、全てのパスでresume()を一回必ず呼んでいることを確かめる必要があります。呼んでいないとコンパイラから警告が来るとのこと。

なお、このようなコンパイラの警告がない[`UnsafeContinuation`](https://developer.apple.com/documentation/swift/unsafecontinuation)もあります。これはパフォーマンス重視の場合に使用しますが、resumeの呼び方を間違っていないかどうか入念なテストがいると思われます。

![截屏2021-07-01 20.22.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/3b150f6d-b476-48d9-cab3-f29b57248219.png)

また、逆に一つのパスでresume()を複数回呼ばないように気をつけましょう。これも警告が出るとのこと。
![截屏2021-07-01 20.24.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/86cece9b-303a-cbba-19cc-7d763d707bb0.png)


既存でデリゲートを使っている非同期処理も、`continuation`をメンバー変数とすることで、awaitの関数に置き換えることができます。

![スクリーンショット 2021-08-16 18.58.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/c7f2a5ab-c01e-c10e-c9cb-813487a8ae6f.png)



#### Explore structured concurrency in Swift
https://developer.apple.com/videos/play/wwdc2021/10134/
https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html

上のセッションでも述べられていましたが、
async をつけると、そこで一旦処理がストップし、先に進まなくなります。asyncをつけた処理が終わると、一旦ストップしていた処理が再開し、次の行に進んでいくことになります。複数のasyncをつけた非同期処理があるとなると、そのままでは、一つ目が終わるまでストップし、その後初めて二つ目のasyncに進むことになります。↓

```swift:.hoge.swift
let (data, _) = try await URLSession.shared.data(for: imageRequest) //終わるまで次の行に行かない
let (metadata, _) = try await URLSession.shared.data(for: metadataRequest) //終わるまで次の行に行かない
```

##### async let
ここで普通のasyncではなく`async let`というものを使うと、`async let`のところで処理が一旦ストップせず、子タスクが作られてそちらで処理される一方、メインの処理はすぐ次の行の処理に進んでいくことになります。そこで処理がサスペンドするわけではないので`await`ないし`try await`は不要となります。のちに変数の中身を参照するとき初めて`await`をつけていくことになります。
例えば以下では、画像データのダウンロードが終わるのを待たず、メタデータのダウンロードも始まり、二つの非同期処理が同時に走ることになります。

```swift:.hoge.swift

func fetchOneThumbnail(withID id: String) async throws -> UIImage {
   async let (data, _) = URLSession.shared.data(for: imageRequest) //子タスクを作り、直ちに次の行に進む
   async let (metadata, _) = URLSession.shared.data(for: metadataRequest) //こタスクを作り、直ちに次の行に進む

   guard 
      let size = parseSize(from: try await metadata), // この時点で初めてtry awaitをつける
      let image = try await UIImage(data?).byPreparingThumbnail(ofSize: size) // この時点で初めてtry awaitをつける
   else {
   //throw some error
   }
}
```

この時、内部的には、`Task`というクラスのオブジェクトが内部的には作られています。
関数`fetchOneThumbnail`が親タスクとなり、その下に子タスク`data` `metadata`がぶら下がる形となるのです。
`Task`は[キャンセル可能](https://developer.apple.com/documentation/swift/task/3851218-cancel)です。しかしキャンセルした時も自動的にTaskがストップするわけではないので、キャンセルがありうるようなケースでは、キャンセルされたかどうかをチェックする必要があります。明示的にキャンセルする場合のほか、Taskを設定したクラスが消滅した場合などにもキャンセルされるので、基本Taskの中には常にキャンセルチェックを入れたほうがいいでしょう。

[`checkCancellation`](https://developer.apple.com/documentation/swift/task/3814826-checkcancellation)
[`isCancelled`](https://developer.apple.com/documentation/swift/task/3814833-iscancelled)

以上はポーリング的にキャンセルされたかを定期的にチェックする感じです。

AsyncSequence内部など、キャンセルされた時に直ちに検知し何かしたい場合は、[`withTaskCancellationHandler`](https://developer.apple.com/documentation/swift/withtaskcancellationhandler(handler:operation:))が利用できます。参考: [WWDC 構造化並行処理の基本を超えて](https://developer.apple.com/jp/videos/play/wwdc2023/10170/?time=224)

- 以下、引用
```swift:Download.swift
import Foundation

class Wrapper<Wrapped> {
    var value: Wrapped
    init(_ value: Wrapped) { self.value = value }
}

func download(url: URL) async throws -> Data? {
    let urlSessionTask: Wrapper<URLSessionTask?> = Wrapper(nil)

    return try await withTaskCancellationHandler {
        try await withUnsafeThrowingContinuation { continuation in
            urlSessionTask.value = URLSession.shared.dataTask(with: url) { data, _, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            }
            urlSessionTask.value?.resume()
        }
    } onCancel: {
        urlSessionTask.value?.cancel()
    }
}

```
- 以上、引用
- [Swift ConcurrencyのwithTaskCancellationHandlerとSendable](https://cockscomb.hatenablog.com/entry/2021/09/26/103128)より

Storeという会社さんが導入した例が以下になります。[open-ai generator](https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/swift5.md)により生成した内容だそうです。

- 以下、引用
```
open class func session(sessionParams: SessionParams? = nil) async throws -> SessionToken {
    var requestTask: RequestTask?
    return try await withTaskCancellationHandler {
        try Task.checkCancellation()
        return try await withCheckedThrowingContinuation { continuation in
            guard !Task.isCancelled else {
                continuation.resume(throwing: CancellationError())
                return
            }

            requestTask = sessionWithRequestBuilder(sessionParams: sessionParams).execute { result in
                switch result {
                case let .success(response):
                    continuation.resume(returning: response.body)
                case let .failure(error):
                    continuation.resume(throwing: error)
                }
            }
        }
    } onCancel: { [requestTask] in
        requestTask?.cancel()
    }
}
```
- 以上、引用 [STORES 予約 アプリへのasync/await導入](https://product.st.inc/entry/2022/12/14/094440)より

##### Group Tasks
動きとしては`async let`と同じく複数の非同期処理を同時並行でやるものとして`group tasks`があります。

[`withTaskGroup`](https://developer.apple.com/documentation/swift/3814991-withtaskgroup)関数や[`withThrowingTaskGroup`](https://developer.apple.com/documentation/swift/3814996-withthrowingtaskgroup)関数でGroupTaskを開始します。

GroupTaskのクロージャ内で[`async`](https://developer.apple.com/documentation/swift/taskgroup/3851242-async)を呼ぶと、子タスクを手動で開始できます。

![截屏2021-07-03 19.43.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/cab297bd-b460-d983-3475-3f54bdddb323.png)

複数の子タスクから結果を集約していくことになると思いますが、実は上のようにthumnailsという辞書型にデータを集めていくようなやり方だとコンパイルエラーが出ます。
![截屏2021-07-03 19.44.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/770a52b3-6900-b34c-9844-132cc8b0801d.png)
子タスクからは変数に同時平行的にアクセスが走る可能性があるので、値型の変数や、actor、同時平行アクセスに対する対処を実装したクラス以外にはアクセスすべきではありません。物によってはこのようにコンパイルエラーが出ます。

下のように`for await`を使って子タスクの結果を集約すれば、コンパイルエラーは出ません。

![截屏2021-07-03 19.48.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/958336a7-86c0-ac9e-30cf-ec21636ae681.png)

##### Unstructured Tasks
以上までのタスク・子タスクの関係性を利用しての同時平行処理を`Structured Tasks`と呼んでいますが、タスクを利用しない同時平行処理を`Unstructured Tasks`と呼びます。

具体的には、例えばApple のUIKitでさまざまに定義されているデリゲートのメソッドなどは`async`で定義されていないので、そのままだと内部で`await`を呼ぶことができません。その際、`async {}`関数を使うと、どこからでも`await`が使えるようになります。

以下は、コレクションビューにおいて、あるアイテムが画面に出現したら画像ダウンロードを開始するが、画面から消えたらキャンセルする一例になります。コレクションビューのデリゲートが`async`なしであるためそのままでは`await`を中で使うことができないので、`async {}`関数を用いて使っています。

![截屏2021-07-03 20.11.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/511e6f52-c14d-693f-771a-1ef4af8c2290.png)
![截屏2021-07-03 20.12.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/8ca79839-0956-6bd9-10a7-07eaa6561c9c.png)

#### Meet AsyncSequence
https://developer.apple.com/videos/play/wwdc2021/10058

`for-await-in`ループまたは`for-try-await-in`で、連続した非同期処理を行うことができます。
![截屏2021-07-10 15.07.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/9dd497b8-0804-a90e-8e42-7e7d49c0b475.png)

`for-await-in`ループを使うには対象が`AsyncSequence`プロトコルに準拠している必要があります。しかし、既存のコールバックやデリゲートを使う処理など、`AsyncSequence`プロトコルに準拠していない物でも、`AsyncStream`でラップしてしまうと、比較的簡単に`for-await-in`ループに適合させられるようになります。

![截屏2021-07-10 15.23.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/6a845236-6b74-1f50-3d05-9b04d3df39bd.png)

実際に使ってみると、以下のようになります。

![截屏2021-07-10 15.24.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/34a12d43-ab90-3e77-6688-e901897db557.png)


### Protect mutable state with Swift actors
https://developer.apple.com/videos/play/wwdc2021/10133/

- Actorとはshared mutable stateを隔離し、データ競合を防ぐもの

データ競合というのが発生することがあります。二箇所（２スレッド）以上の箇所から同じデータにアクセスし、しかも読み込みではなく書き込みが一つ以上含まれる場合は、データ競合が起こり得ます。

値型(基本型や構造体など)のデータであれば、複数の箇所から参照されるということが起こり得ない（常にローカルでコピーが作られる）ので、データ競合は起こり得ません。が、他方で複数の場所から参照できる共有の状態というのは、値型では実現できないということになります。

というわけで今までは、複数箇所から同時並行的に参照・変更され得るような、共有のデータ（`Shared mutable state in concurrent programs`）を実現するには、参照型（クラスなど）で競合が起きないようDispatchQueueなどを用いるなどして注意深くコードを書くしかありませんでした。

今回導入される`Actor`では、`Shared Mutable State`を簡単に実現できるように設計されたものです。`Actor`自身の状態を他のプログラム全ての部分から隔離する設計となっています。

`Actor`タイプはクラスと同様参照型で、クラスや構造体と同様変数・関数・イニシャライザ・エクステンションなどを定義することができます。

以下のように、データ競合を避けるため、`Actor`へアクセスする際はawaitキーワードで待受をする必要があります。
![スクリーンショット 2021-08-19 18.54.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/224c1529-aafd-0c90-2e1d-cec89a16b20f.png)

他方`Actor`自身が`Actor`自身にアクセスするときは特に待受する必要はありません。
![スクリーンショット 2021-08-19 18.55.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/6c25e732-cd05-fbc7-e15b-e265f10ce079.png)

- データが想定と異なる変更をされる可能性があることに注意

注意点として、`Actor`は複数箇所から（awaitで）アクセスされることから、awaitで待っている間に他の箇所からのアクセスにより`Actor`の状態が当初の予想から変わってしまっていることがあります。そのためそれをあらかじめ想定しておく必要があります。

例えば以下は、画像をダウンロードするためのActorです。

まず、キャッシュをチェックし、キャッシュがある場合はダウンロードをせずにキャッシュを返しています。(`if let cached = chche[url] { return cached }`)

もしキャッシュがなかったら初めてダウンロードをし、ダウンロードした画像をキャッシュに入れていくわけです。

最初の段階でキャッシュがないことをチェックしてはいます。しかし、ダウンロードが時間がかかる上、他の箇所からもこの部分のコードを通って同時にダウンロードをしている可能性があります。すると他の箇所から実行されているダウンロードが先に完了し、キャッシュを埋めている可能性があります。つまり最初の行でキャッシュがないことを確認していたとしても、自分の画像ダウンロードが終わった時には、他の画像でキャッシュが埋められている可能性があるわけです。

そのため、ダウンロードが終わった後(時間がかかる処理が終わった後)も、もう一度キャッシュをチェックする処理を入れています。そうして、キャッシュが入っていた時は元のキャッシュを尊重してダウンロードしてきた画像は破棄する、という例が紹介されています。
(`cache[url] = cache[url, default: image]; return cache[url]`)

![スクリーンショット 2021-08-19 18.56.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/4b8e8579-e8b0-ca7b-3789-a934def94397.png)
![スクリーンショット 2021-08-19 18.59.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/35c34bc7-6042-f09b-fb12-82f6cd82f1a9.png)

- nonisolated キーワードを使ったプロトコルへの適合

actorをプロトコルに適合させる際問題が発生することがあります。何かactor内にある状態をにアクセスするようなメソッドを実装しなければいけないのに、awaitキーワードをつけることができないので、結果プロトコルに適合できないというような場合です。

![スクリーンショット 2021-08-19 19.04.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/483141f3-910b-3099-0abe-6c5452c9ddc4.png)

この場合は、そのメソッドだけ`nonisolated`キーワードをつけると対応できます。`nonisolated`キーワードをつけるということは、その部分は`Actor`の外側として扱われるので、`Actor`の内側にある状態(`mutable shared state`)にアクセスすることはできません。下の例では、定数の`idNumber`にアクセスしているだけなので問題は生じません。

![スクリーンショット 2021-08-19 19.04.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/0f78e465-599d-ca09-8de7-607e726c041d.png)

もし`nonisolated`をつけているところからActorの変数にアクセスしようとすると、`mutable shared state`にアクセスしようとしていることになるので、コンパイルエラーになります。
![スクリーンショット 2021-08-19 19.05.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/242e6e20-0d8a-86e9-1fae-1b5267da2af5.png)

- Actorが所有できるのはSendableプロトコルに適合した型のみ

もしActorが何かのクラス（以下では`Book`クラス）の変数を所有していたとしましょう。するとクラスは参照型なので、他の箇所からもアクセスできてしまいます。つまり、Actorの内部の状態がActorを経ずに参照されてしまうということになります。
![スクリーンショット 2021-08-19 19.10.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/07405d9a-4965-667d-7c2f-1683b90c7037.png)

これを防ぐために、`Actor`が保有できるのは新しく導入された`Sendable`プロトコルに適合した型のみとされています。`Sendable`は複数スレッドからの実行の文脈で安全に共有できるかどうかの性質を表しています。値型（構造体・基本型など）や、Actorや、immutableなクラスが適合しています。言い換えれば、普通のクラスは`Sendable`に適合していません。従って、Actorは変数として普通のクラスを持つことはできません。
![スクリーンショット 2021-08-19 19.12.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/b5f6201d-5408-e860-a643-96b9a251e31b.png)

なお`Sendable`に適合するには、ある型の持っている変数が全て`Sendable`に適合している必要があります。また、ジェネリックを用いる型を`Sendable`に適合したい場合は、ジェネリックで導入する型が全て`Sendable`に適合している必要があります。
![スクリーンショット 2021-08-19 19.13.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/c93a277e-7039-87ad-c3ab-b7b43283dfa9.png)

メソッド・クロージャを`Sendable`に適合させるには`@Sendable`でマーカーをつけます。`@Sendable`メソッドは値型の変数しか使うことができません。メソッドがキャプチャする対象も`Sendable`プロトコルに適合している必要があります。以下では、`@Sendable`のクロージャの中でmutable関数を使っているのでエラーとなっています。

![スクリーンショット 2021-08-19 19.16.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/45948123-e006-a29c-404e-cb4c0d2426e9.png)

- main actor

メインスレッド（UIの更新などを行うスレッド）というものがありました。今まで、例えばAPIなどでデータを持ってきた後、そのデータを用いてUIを更新する場合は、明示的にメインスレッドで行うようにするため、`DispatchQueue.main.async {}`等を使っていたかと思います。

今回導入された`main actor`はこれを簡単にできるようになっています。関数や変数に`@MainActor`という文字をつけると、それらの実行は常にメインスレッド上で行われることが保証されます。クラスの宣言などに`@MainActor`をつけた場合は、そのクラスの全変数・メソッドに`@MainActor`をつけたのと同じになります。

![スクリーンショット 2021-08-19 19.21.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/2e9898bb-d994-a344-1633-3b393bda106a.png)
![スクリーンショット 2021-08-19 19.22.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/6401e092-e731-eea3-e078-526f7ca00fd0.png)

プログラムの中で、データを扱う部分の中でUIと接する部分（データを用いてUIに影響を与える部分）に`@MainActor`を使っていくと良いでしょう。


### Discover concurrency in SwiftUI
https://developer.apple.com/videos/play/wwdc2021/10019/

async マークの付いた非同期メソッド(サーバーから画像をダウンロードする) を持っている、[ObservableObject](https://developer.apple.com/documentation/combine/observableobject)に`@MainActor`マークを つける例が紹介されています。
![截屏2021-09-14 22.26.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/8c07fad4-122f-221f-3127-3f2907bbae7f.png)


これにより、該当の オブジェクトのメソッドが常にメインスレッド上で実行されることが保証されます。async キーワードをつけていることにより、非同期処理と実行中は一旦メインスレッドが明け渡しされるので、UIが止まってしまう心配はありません。非同期処理の終了次第、画面の更新の処理がメインスレッドで確実に行われることになります。

![截屏2021-09-14 21.28.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392332/b8a6fc69-e6b8-bd87-89c5-199663e17a0e.png)


※ObservableObjectについては[こちら　whats-the-difference-between-observedobject-state-and-environmentobject](https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject)参照

# Swift Concurrency

公式解説ページ: [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Sendable-Types)
[Swift Migration Guide](https://swift-migration-guide.jp/documentation/swift-6-concurrency-migration-guide-----/dataracesafety)

## Data Isolation(隔離)

データ競合が起きないことを保証する仕組みのことで、コンパイラがあらゆる可変な状態をチェックしている。

## Isolatioin Domain(隔離ドメイン)

全ての変数・関数について、隔離を行うために三種の隔離ドメインの内のどれかに属することになる。どの隔離ドメインに属するかは明示的に示すこともできるし、宣言されている場所のコンテキストによって暗黙的に決まっている場合もある。

1. 非隔離（non-isolated）
2. アクター隔離(actor-isolated)
3. グローバルアクター隔離(global-actor-isolated)　典型的には、MainActor-isolated

actor内部であれば2, @MainActorなどグローバルアクターの内部であれば3, それ以外は全て1となる。

ある隔離ドメインはそれぞれ保護されており（Isolation Boundary 隔離境界),ある隔離ドメインのコンテキストの中から、別の隔離ドメインに属する変数や関数にアクセスしようとするとコンパイラによってエラーとなる。これにより、データ競合を防いでいる。

## Sendable

[Sendable Types](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Sendable-Types)
複数スレッドからの実行の文脈で安全に共有できることを表しているプロトコル。これに適合している型は隔離境界を越えることができる。

## Sending

Sendableに適合していない型であっても、場合によっては安全に実行できる場合があり、その場合Sendable キーワードをつけることになる。一々Sendableに適合させなくても良い点が便利。

関数のパラメータにつける場合と、関数の戻り値につける場合がある。

関数のパラメータの場合、その値を関数に渡した後一才変更されない必要がある。

[Swift 6 Migration Guide - Flow-Sensitive-Isolation-Analysis](https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety/#Flow-Sensitive-Isolation-Analysis)
[swift-evolution](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0430-transferring-parameters-and-results.md)
[【Swift Concurrency】引数に付与するsendingキーワードについて
]()


# その他参照

[Swift Documentation - Task Cancellation](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Task-Cancellation)
[Swift Documentation - Task and Task Group](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Tasks-and-Task-Groups)
[Swift Documentation - Actors](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Actors)