Swiftでasync/awaitとTaskを使うときに最初に見ること

Swiftで非同期処理を書くなら、まず async/awaitTask の関係を見ておくと整理しやすいです。

Swift公式のConcurrencyドキュメントでも、非同期関数には async を付け、呼び出し側では await する形が説明されています。

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/

async関数を書く

func fetchName() async -> String {
    "Makonote"
}

async が付いた関数は、呼び出し側で await が必要です。

let name = await fetchName()

ただし、await は非同期コンテキストの中でしか使えません。

Taskから呼ぶ

同期的な場所から非同期処理を始める場合は Task を使います。

Task {
    let name = await fetchName()
    print(name)
}

SwiftUIのボタンやライフサイクルから非同期処理を起動するときに、よくこの形になります。

Button("Load") {
    Task {
        await load()
    }
}

UI更新はMainActorで扱う

UIに反映する値を更新する場合は、MainActorを意識します。

@MainActor
final class ViewModel: ObservableObject {
    @Published var name = ""

    func load() async {
        name = await fetchName()
    }
}

@MainActor を付けると、その型の処理はメインアクター上で実行されます。

UIの状態を持つViewModelでは、この形が扱いやすいことが多いです。

Taskを増やしすぎない

とりあえず Task {} で囲むと動くことがあります。

ただ、あちこちにTaskを作ると、どこでキャンセルされるのか、どの順番で完了するのかが見えにくくなります。

非同期処理の入口だけ Task にして、中の処理は async 関数として分ける方が追いやすいです。

Button("Load") {
    Task {
        await viewModel.load()
    }
}

まとめ

Swiftの非同期処理は、まずこの形で見ます。

  • 非同期関数には async
  • 呼び出し側は await
  • 同期的な入口から始めるなら Task
  • UI更新は @MainActor
  • Task は入口に寄せる

細かいキャンセルや並列実行に進む前に、この基本形を作っておくと読みやすくなります。

関連記事

参考