🥑

【Swift】コールバック関数を async / await に変換【Concurrency】

2021/12/21に公開

WWDC2021 で発表された Concurrency (並行処理)が、ついにiOS 13でも使えるようになりました。
そのおかげで iOS13 からでも非同期処理、並行処理のコードをより簡潔に、より安全に書くことができるようになりました。

しかし、全てのメソッドがすぐに async / await になるわけではありません。
自作メソッドやサードパーティライブラリの非同期APIがコールバックのままになっているかもしれません。

手元にあるのはコールバック関数だけで、 async / await ができる関数が今すぐにでも欲しい!!

そんなあなたに向けてこの記事を書きました。

環境

Xcode 13.2.1
Swift 5.5

結論

標準ライブラリに新たに登場した withCheckedContinuataion または withCheckedThrowingContinuation という関数を使います。

withCheckedContinuationはこちら
https://developer.apple.com/documentation/swift/3814988-withcheckedcontinuation

withCheckedThrowingContinuationはこちら
https://developer.apple.com/documentation/swift/3814989-withcheckedthrowingcontinuation

引数がシンプルなコールバックの場合

例として、func downloadURL(completion: (URL) -> ())async / await に対応させてみます。
throws は必要ないので、 withCheckedContinuation 関数を使って async / await 対応させてみましょう

// 変換前の関数
func downloadURL(completion: (URL) -> ())  // (1)

// 変換後の関数
func downloadURL() async -> URL {
    await withCheckedContinuation { continuation in
        downloadURL { url in 
	    continuation.resume(returning: url)
	}
    }
}

Throwsが必要なコールバックの場合

Errorが帰ってくるかもしれない非同期処理の場合は、 withCheckedThrowingContinuation 関数を使いましょう。

func downloadData(completion: (Data?, Error?) -> ())async / await に対応させてみます。

// 変換前の関数
func downloadData(from url: URL,
    completion: @escaping (Data?, Error?) -> Void) // (2)
    
// 変換後の関数
func downloadData(from url: URL) async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        downloadData(from: url) { data, error in
	    if let error = error {
	        continuation.resume(returning: data)
	    } else {
	        continuation.resume(throwing: error)
            }
        }
    }
}

コールバックがResult型の場合

コールバックがResult型の場合は、さらに簡潔に書けるようになります。

func downloadData(completion: (Result<Data, Error>) -> ())async / await に対応させてみます。

// 変換前の関数
func downloadData(completion: (Result<Data, Error>) -> ())

// 変換後の関数
func donwloadData() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in 
        downloadData(from: URL) { result in 
	    continuation.resume(with: result)
	}
    }
}

まとめ

コールバック関数を throws する必要がなければ withCheckedContinuation 関数を。 throws する必要があれば、 withCheckedThrowingContinuation 関数を使ってラップしてあげましょう。

間違っている点があれば、DMで教えてください!!

twitter: @youandtaichi
Instagram: @youandtaichi
Scrapbox: /youandtaichi

Discussion