💬

async let の使い方

に公開

async letとは

Swiftのasync letは、構造化並行性(Structured Concurrency)に含まれる構文で、独立した非同期処理を同時に実行するために使われます。
順序性のない非同期処理を効率よく並列で走らせることができます。

使い方

async letを使うと、非同期関数の処理を並行して開始し、その結果を後で受け取るための変数を定義できます。
値を実際に使うときには、awaitを使って結果が返るのを待つ必要があります。

async let a = fetchA()
async let b = fetchB()

await print(a)
await prnit(b)

注意:引数にletを使う必要がある

非同期関数に引数を渡す場合、その引数には let(不変)で宣言した値を使う必要があります。

func loadUser() async {
// varではなくletを使う⚠️⚠️⚠️
    let user = User(id: UUID(), name: "Taylor Swift")
    async let result = fetchUserName(user)
}

async letは子タスク(child task)を内部的に生成しており、
このとき、渡された変数はキャプチャ(クロージャと同様)されます。
varの場合、非同期処理が走る前に値が変更される可能性があり、意図しない動作につながってしまいます。したがって、let(不変)で宣言した値を使う必要があります。

下記のサンプルコードは、変更された値が使われてしまう実例です。

struct User {
    var id: UUID
    var name: String
}

func fetchUserName(_ user: User) async -> String {
    try? await Task.sleep(for: .seconds(1))
    print("ユーザー名:\(user.name)")
    return user.name
}

func loadUser() async {
    var user = User(id: UUID(), name: "Taylor Swift")
    async let result = fetchUserName(user)
// 値を変更 
    user.name = "Taylor Kotlin"
    await print(result) // Taylor Kotlin
}

これはuserがキャプチャされてしまっているためです。
Swiftは安全性を最優先するため、通常ならコンパイルエラーになる場面ですが、Swift6モードでは警告も出ないので注意が必要です。

また、Taskでも同様の問題が起こりえます

func loadUserWithTask() {
    var user = User(id: UUID(), name: "Taylor Swift")
    Task {
        let result = await fetchUserName(user)
        print(result) // Taylor Kotlin
    }
// 値の変更
    user.name = "Taylor Kotlin"
}

Taskasync letと同様にキャプチャを伴う非同期実行です。
こちらもvarを変更すると予期しない結果になる可能性があり、しかもコンパイル警告が出ないので注意する必要があります。

サンプルコード全体
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "swift")
                .resizable()
                .frame(width: 100, height: 100)
                .onTapGesture {
                    Task {
                        await loadUser()
                    }
                    loadUserWithTask()
                }
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

struct User {
    var id: UUID
    var name: String
}

func loadUser() async {
    var user = User(id: UUID(), name: "Taylor Swift")
    async let result = fetchUserName(user)
    user.name = "Taylor Kotlin"
    await print(result)
}

func loadUserWithTask() {
    var user = User(id: UUID(), name: "Taylor Swift")
    Task {
        let result = await fetchUserName(user)
        print(result)
    }
    user.name = "Taylor Kotlin"
}

func fetchUserName(_ user: User) async -> String {
    try? await Task.sleep(for: .seconds(1))
    print("ユーザー名:\(user.name)")
    return user.name
}

最後までお読みいただきありがとうございます。
もし間違い等ございましたら、ご指摘いただけると幸いです。

Discussion