[Xcode/Swift] GPT先生からSwift Concurrencyを学んだ② (async let)

async letとは

async let = 非同期処理を「同時に始めて、後でまとめて受け取れる」仕組み。

Swift Concurrencyを使えば、複数の非同期処理を同時に実行して、あとで結果をまとめて取得するのも楽。

async let のメリット

  1. 同時に複数の処理を走らせられる(並列処理)
  2. コードがシンプル、読みやすい
  3. 順番に依存しない処理にGood

サンプルコードでざっくり学ぶ

3つのリクエストを同時に実行する

import SwiftUI

struct DummyUser: Codable, Identifiable {
    let id: Int
    let name: String
}

struct DummyPost: Codable, Identifiable {
    let id: Int
    let userId: Int
    let title: String
}

struct DummyComment: Codable, Identifiable {
    let id: Int
    let postId: Int
    let body: String
}

struct HomeView: View {
    @State private var user: DummyUser?
    @State private var posts: [DummyPost] = []
    @State private var comments: [DummyComment] = []
    @State private var isLoading = false
    @State private var errorMessage: String?

    var body: some View {
        VStack(spacing: 16) {
            if isLoading {
                ProgressView("Loading...")
            } else {
                if let user = user {
                    Text("User: \(user.name)")
                }
                Text("📝 投稿数: \(posts.count)")
                Text("💬 コメント数: \(comments.count)")
                Button("Fetch Data") {
                    Task {
                        await fetchAllData()
                    }
                }
            }
            if let errorMessage = errorMessage {
                Text("Error: \(errorMessage)")
                    .foregroundColor(.red)
                    .padding()
            }
        }
        .padding()
    }

    // 本番ではアーキテクチャに沿ってViewModel等で処理しましょう、今回はわかりやすさ重視
    private func fetchAllData() async {
        isLoading = true
        do {
            // Point: async letは宣言時点で非同期処理を開始するため、複数のAPI呼び出しを同時に行える
            async let user = fetchUser()
            async let posts = fetchPosts()
            async let comments = fetchComments()

            // 結果が返る順番は保証されないが、全ての非同期処理が完了するまで待機する、順番も制御したい場合はTaskGroupを使用する(これは次章以降に続く)
            self.user = try await user
            self.posts = try await posts
            self.comments = try await comments
            isLoading = false
        } catch {
            // 非同期リクエストのうち一つでも失敗した場合、catchブロックが実行される
            isLoading = false
            errorMessage = "データの取得に失敗しました: \(error.localizedDescription)"
        }
    }

    private func fetchUser() async throws -> DummyUser {
        let url = URL(string: "https://jsonplaceholder.typicode.com/users/1")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(DummyUser.self, from: data)
    }

    private func fetchPosts() async throws -> [DummyPost] {
        let randomUserNumber = Int.random(in: 1...10)
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts?userId=1")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([DummyPost].self, from: data)
    }

    private func fetchComments() async throws -> [DummyComment] {
        let url = URL(string: "https://jsonplaceholder.typicode.com/comments?postId=1")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([DummyComment].self, from: data)
    }
}

まとめ

  1. async let を使えば、複数の非同期処理を同時にスタートできる
  2. 処理が終わるまで try await後からまとめて結果を取得
  3. async let宣言した瞬間に実行される(=即時スタート)
  4. 順番に依存しない処理(ユーザー取得→投稿取得→コメント取得みたいな)に最適
  5. ただし、どれか1つが失敗したら他もキャンセルされる点には注意

次回はTaskGroupで大量並列をやっていきましょう。

[Xcode/Swift] GPT先生からSwift Concurrencyを学んだ③ (TaskGroup)