見出し画像

Swift Concurrencyの基礎

こんばんは!りーさんです。
今回はSwift Concurrencyについて書いていこうと思います。

Swift Concurrencyとは

Swift Concurrency(スイフト コンカレンシー)は、Swift 5.5で導入された新しい並行処理のモデルです。このモデルは、より簡単で安全な非同期コードの記述を可能にします。主要なコンセプトはasync(エーシンク)とawait(アウェイト)、およびactors(アクター)です。

メリット
• コードが直線的で読みやすくなる。
• 非同期処理のエラーハンドリングが簡単になる。
• アクターを使って、データの競合や不正アクセスを防止することができる。

Swift Concurrencyは、従来のコールバックやクロージャベースの非同期処理よりも、コードの可読性と安全性を大幅に向上させます。

同期処理とは

レストランで例えるとあなたがレストランに行って、注文をします。そして料理が来るまでずっと待っている状態になります。(この間何もあなたは何もできません)

非同期処理とは

こちらもレストランで例えるとあなたがレストランに行って、注文をします。そして料理が出来上がったらウェイターがあなたに料理を持ってきます。それまでの間、あなたは他のこと(友達と話す、携帯を見るなど)ができます。

Async/Await

今までのコールバックによる非同期

func makeOrder(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        sleep(3)
        let result = "料理ができました"

        // メインスレッドでコールバックを呼び出す
        DispatchQueue.main.async {
            completion(result)
        }
    }
}

makeOrder { result in
    print(result)
}

// 料理ができるまで他の作業ができる
print("注文完了、料理を待っている間に他の作業をします")

Concurrencyを使った非同期

func makeOrder() async -> String {
    // 非同期処理をシミュレート
    await Task.sleep(3 * 1_000_000_000) // 3秒待つ
    return "料理ができました"
}


Task {
    let result = await makeOrder()
    print(result)
}

// 料理ができるまで他の作業ができる
print("注文完了、料理を待っている間に他の作業をします")

非同期処理が直線的に記述され、読みやすさと保守性が向上します。

既存のコールバックベースの非同期APIを変換する

// 既存のコールバックベースの非同期関数
func makeOrder(completion: @escaping (Result<String, Error>) -> Void) {
    DispatchQueue.global().async {
        sleep(3)
        
        // 成功するかどうかをランダムに決定
        if Bool.random() {
            let result = "料理ができました"
            DispatchQueue.main.async {
                completion(.success(result))
            }
        } else {
            let error = NSError(domain: "OrderError", code: 1, userInfo: nil)
            DispatchQueue.main.async {
                completion(.failure(error))
            }
        }
    }
}

// withCheckedThrowingContinuationを使ってasync/awaitをサポートする関数に変換
func makeOrderAsync() async throws -> String {
    try await withCheckedThrowingContinuation { continuation in
        makeOrder { result in
            switch result {
            case .success(let message):
                continuation.resume(returning: message)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

func placeOrder() async {
    do {
        let result = try await makeOrderAsync()
        print(result)
    } catch {
        print("エラーが発生しました: \(error)")
    }
}

Task {
    await placeOrder()
}

// 料理ができるまで他の作業ができる
print("注文完了、料理を待っている間に他の作業をします")

withCheckedThrowingContinuation
makeOrderAsync関数内でwithCheckedThrowingContinuationを使い、コールバックのmakeOrder関数をラップします。
• continuation.resume(returning:)を使って成功時の結果を返します。
• continuation.resume(throwing:)を使ってエラーを返します。

async let

import Foundation

// 非同期関数の定義
func fetchOrder1() async -> String {
    await Task.sleep(2 * 1_000_000_000) // 2秒待つ
    return "料理1ができました"
}

func fetchOrder2() async -> String {
    await Task.sleep(3 * 1_000_000_000) // 3秒待つ
    return "料理2ができました"
}

func fetchOrder3() async -> String {
    await Task.sleep(1 * 1_000_000_000) // 1秒待つ
    return "料理3ができました"
}

func placeOrders() async {
    // 複数の非同期タスクを並行して実行
    async let order1 = fetchOrder1()
    async let order2 = fetchOrder2()
    async let order3 = fetchOrder3()
    
    // すべての結果を待つ
    let result1 = await order1
    let result2 = await order2
    let result3 = await order3
    
    // 結果を出力
    print(result1)
    print(result2)
    print(result3)
}

Task {
    await placeOrders()
}

// 料理ができるまで他の作業ができる
print("注文完了、料理を待っている間に他の作業をします")

複数の非同期タスクを効率的に並行して実行し、それらの結果を待つことができます。これにより、非同期処理のパフォーマンスが向上し、全体の待ち時間が短縮されます。


Task

Task.init(priority:operation)

基本的な非同期タスクです。Taskオブジェクトは独立して実行されます。同期処理に混ぜて書くことが可能です。

Task {
    await doSomething()
}

Task.detached(priority:operation)

親タスクや現在のコンテキストと独立した非同期タスクを作成します。特定の優先度で実行されます。

// 優先度順に非同期タスクを作成して実行
func performTasks() async {
    // 高優先度のタスク
    Task.detached(priority: .high) {
        let data = await doSomething()
        print(data)
    }

    // 中優先度のタスク
    Task.detached(priority: .medium) {
        let data = await doSomething()
        print(data)
    }

    // 低優先度のタスク
    Task.detached(priority: .low) {
        let data = await doSomething()
        print(data)
    }
}

TaskGroup

タスクグループを使うと、複数のタスクをグループ化して管理できます。TaskGroupは複数の並行タスクをまとめて実行し、すべてのタスクが完了するまで待つことができます。

func performTasks() async {
    await withTaskGroup(of: Void.self) { taskGroup in
        taskGroup.addTask {
            await doSomething()
        }
        taskGroup.addTask {
            await doSomething()
        }
    }
    print("All tasks completed")
}

Task {
    await performTasks()
}

ThrowingTaskGroup

ThrowingTaskGroupは、タスクグループの中でエラーを投げることができるタスクを管理します。エラーハンドリングが必要な場合に使用します。

func performTasksWithThrowing() async throws {
    try await withThrowingTaskGroup(of: Void.self) { taskGroup in
        taskGroup.addTask {
            try await doSomethingWithError()
        }
        taskGroup.addTask {
            try await doSomethingWithError()
        }
    }
    print("All tasks completed")
}

func doSomethingWithError() async throws {
    if Bool.random() {
        throw NSError(domain: "ErrorDomain", code: 1, userInfo: nil)
    }
    print("Task is running with error handling")
    await Task.sleep(1 * 1_000_000_000) // 1秒待つ
    print("Task completed")
}

Task {
    do {
        try await performTasksWithThrowing()
    } catch {
        print("An error occurred: \(error)")
    }
}

Actor

Actorを使用することで、共有されたデータに対するアクセスを安全に行うことができます。

データ競合を防ぐActor

actor Counter {
    var value = 0

    func increment() -> Int {
        value = value + 1
        return value
    }
}

その内部の状態に直接アクセスできるのは1つのタスク(スレッド)だけです。これにより、データ競合を回避し、安全な並行処理を実現します。

MainActor

UIなどの更新を行う際にメインスレッドで実行したい場合に使用します。

Task { @MainActor in
  self.showAlertView = true
}

Taskごとではなく、特定のクラス、構造体、列挙型、またはそれらのメソッドやプロパティがメインスレッド(メインアクター)で実行されることを保証するために使用することもできます。

@MainActor
func action() {
  self.showAlertView = true
}


以上になります!Swift Concurrency自分も使いこなせるようにしていきたいと思います!

この記事が気に入ったらサポートをしてみませんか?