【Swift Concurrency】withCheckedContinuationとwithCheckedThrowingContinuationの使い方

この記事からわかること

  • Swift Concurrencyとは?
  • withCheckedContinuationwithCheckedThrowingContinuation使い方
  • 非同期処理実装方法
  • asyncとの違い

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

公式リファレンス:Concurrency
公式ドキュメント:Concurrency

Swift Concurrencyとは?

Swift Concurrency(同時実効性)とはiOS15(Swift 5.5)から導入された仕組みの1つで非同期プログラミング(並列処理)をより利用しやすくするための機能を提供しています。asyncawaitキーワード、Task構造体などはSwift Concurrencyから提供されており、非同期処理を実装する際に利用されるcompletionHandler(コールバック関数)の弱みである可読性の低下を解消することができるようになりました。

今回はその中のwithCheckedContinuationwithCheckedThrowingContinuationについてまとめていきます。

withCheckedContinuationとwithCheckedThrowingContinuationの使い所

先にwithCheckedContinuationwithCheckedThrowingContinuationの使い所と役割を整理しておきます。この2つのメソッドはSwift Concurrencyから提供されている非同期処理を実装するためのメソッドです。といっても非同期処理の実装はasync/awaitを使用すれば実装できます。

両者のメソッドの引数と見てみるとそのまま別の関数(クロージャー)を渡せるようになっています。

公式リファレンス:withCheckedContinuationメソッド

func withCheckedContinuation<T>(
    function: String = #function,
    _ body: (CheckedContinuation<T, Never>) -> Void
) async -> T

なので使い所としては既存のコードを非同期関数に簡単に改修したい場合に活用できます。Swift Concurrencyを導入せずにcompletionHandler(コールバック関数)などで非同期的な処理を実装していた場合に、コード量が多かったり、複雑だとasync/awaitに対応するコストが大きくなってしまいます。これを時短できるのがwithCheckedContinuationwithCheckedThrowingContinuationになるようです。

それぞれの違いは引数に渡す関数がエラーをthrowsするかしないかなので実装方法はほとんど同じです。

実装例

例えばまず既存のコードでにサーバーからデータを取得するfetchDataメソッドがあるとします。これはcompletionHandler(コールバック関数)で今までは実装されていました。

func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    // 実際はAPIリクエストを送信
    sleep(2)
    if Bool.random() {
        completion(.success(Data()))
    } else {
        completion(.failure(NSError(domain: "ErrorDomain", code: 42, userInfo: nil)))
    }
}

これを非同期関数として改修したい場合に上記の場合は簡素なのでasync/awaitでも簡単ですが、もっと複雑なコードになっていると大変なのでwithCheckedThrowingContinuationを使用します。

func asyncFetchData() async throws -> Data {
    return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Data, Error>) in
        fetchData { dataResult in
            switch dataResult {
            case .success(let data):
                // 非同期処理が成功した場合
                continuation.resume(returning: data)
            case .failure(let error):
                // 非同期処理が失敗した場合
                continuation.resume(throwing: error)
            }
        }
    }
}

Task {
    let result = try await asyncFetchData()
    print(result) // 0 bytes
}

このように元の関数をいじることなくラップするだけで非同期関数として実装することが可能になります。エラーを投げない場合はwithCheckedContinuationを使用するだけです。

引数:CheckedContinuation<T, Never>

公式リファレンス:CheckedContinuation構造体

引数bodyでは(CheckedContinuation<T, Never>) -> Void形式(クロージャー)になっています。クロージャーの引数として取得できるCheckedContinuation型は同期的な元の関数を非同期関数として繋ぎ込むためのインターフェース的な役割を持っています。

resumeメソッドを使用して非同期関数としての結果を操作します。

// 非同期処理が成功した場合
continuation.resume(returning: data)
// 非同期処理が失敗した場合
continuation.resume(throwing: error)

おまけ:async/awaitに置き換える

ちなみに先ほどのfetchDataasync/awaitに置き換えると以下のような感じでしょうか?

func fetchData() async throws -> Data {
    // 実際はAPIリクエストを送信
    try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 2秒待機 (1秒 = 1_000_000_000ナノ秒)

    if Bool.random() {
        return Data()
    } else {
        throw NSError(domain: "ErrorDomain", code: 42, userInfo: nil)
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

参考文献:Studyplus iOSアプリにasync/awaitを導入してみた

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index