最初に: こちらはasync/await学習後、Combineを学習、練習中の考えを文章作成のメインは怠けてAIに頼んで一部自分で修正して作成されたものです。
はじめに
Swift の開発者にとって、非同期プログラミングは常に重要なテーマです。Swift 5.5 で導入された async/await は、非同期コードをより読みやすく、理解しやすいものにしています。しかし、非同期プログラミングは他にもCombine フレームワークやRxSwift、コールバックベースのアプローチを使用しています。この記事では、古い非同期パターンを async/await スタイルに移行する方法、Combineとasync/awaitの使用ケースに焦点を当てます。
Combine のサンプルコード
Combineフレームワークを使用した非同期コードの例として、以下の PHPhotoLibrary の拡張機能を見てみましょう。このコードは、ユーザーが写真ライブラリへのアクセスを許可しているかどうかを確認します。
import Foundation
import Photos
import Combine
extension PHPhotoLibrary {
static var isAuthorized: Future<Bool, Never> {
Future { resolve in
Self.fetchAuthorizationStatus { status in
resolve(.success(status))
}
}
}
static func fetchAuthorizationStatus(callback: @escaping (Bool) -> Void) {
let currentlyAuthorized = authorizationStatus() == .authorized
guard !currentlyAuthorized else {
return callback(currentlyAuthorized)
}
requestAuthorization { newStatus in
callback(newStatus == .authorized)
}
}
}
このコードでは、Combineの Future を使用して、写真ライブラリへのアクセス権限の状態を非同期的に取得しています。
async/await への移行
Swiftのasync/awaitを用いると、上記のコードは次のように書き換えられます。
extension PHPhotoLibrary {
static func isAuthorizedAsync() async -> Bool {
let status = authorizationStatus()
switch status {
case .authorized:
return true
case .notDetermined:
return await requestAuthorizationAsync()
default:
return false
}
}
static func requestAuthorizationAsync() async -> Bool {
await withCheckedContinuation { continuation in
requestAuthorization { newStatus in
continuation.resume(returning: newStatus == .authorized)
}
}
}
}
このコードでは、async/await
と withCheckedContinuation
を組み合わせて、非同期のアクセス権限チェックを行っています。これにより、コードの可読性が向上し、より簡潔になります。
利用方法の前後変化
Before
MyView()
.onAppear {
PHPhotoLibrary.isAuthorized
.receive(on: DispatchQueue.main)
.sink { isAuthorized in
if isAuthorized {
self.photos = model.loadPhotos()
} else {
isDisplayingError = true
}
}
.store(in: &subscriptions)
}
After
.task {
let isAuthorized = await PHPhotoLibrary.isAuthorized()
if isAuthorized {
self.photos = model.loadPhotos()
} else {
isDisplayingError = true
}
}
withCheckedContinuation の解説
withCheckedContinuation
は Swift の async/await モデルで、従来のコールバックベースの非同期 API を適応するための機構です。コールバック(completion handler)を介して結果を返す非同期関数があり、その関数を新しい async/await ベースのコードで使用したい場合に非常に役立ちます。
基本原則
Swift の並行モデルでは、async/await を使用して非同期コードを同期コードのように書くことができます。しかし、多くの古いAPIはコールバックベースのままです。withCheckedContinuation
は、これらの場合に橋渡しを提供します。この機構は「継続(continuation)」を作成し、コールバック内でこの continuation を再開できます。
使用方法
-
Continuation の作成:
withCheckedContinuation
を呼び出すと、渡されたクロージャがすぐに実行され、このクロージャに continuation パラメータが提供されます。この continuation は後で非同期操作を再開するために使用できます。 -
Continuation の再開: 非同期操作が完了すると(例えば、コールバックがトリガーされた場合)、continuation の
resume(returning:)
メソッドを使用して結果を返すことができます。これにより、コールバックスタイルの非同期コードを async/await スタイルに変換できます。
例
コールバックベースの関数の例:
func fetchSomething(completion: @escaping (Result<String, Error>) -> Void) {
// 非同期操作...
}
withCheckedContinuation
を使用して async/await スタイルに変換する例:
func fetchSomethingAsync() async -> Result<String, Error> {
await withCheckedContinuation { continuation in
fetchSomething { result in
continuation.resume(returning: result)
}
}
}
この例では、fetchSomethingAsync は async 関数で、内部で withCheckedContinuation を使用して、コールバックベースの fetchSomething を呼び出しています。fetchSomething のコールバックがトリガーされた時、continuation.resume(returning:) を使って非同期操作を再開し、結果を渡します。
注意事項
-
懸念の回避: continuation は最終的に再開されることが必要です。そうでないと、プログラムが待機状態で停止する可能性があります(懸念)。各 continuation は一度だけ再開されるべきです。
-
スレッド安全: withCheckedContinuation とその resume はスレッドセーフです。異なるスレッドで continuation を再開することができます。
-
withCheckedContinuation は、古い非同期コードを新しい async/await モデルにスムーズに移行する方法を提供し、非同期コードをより整理され、理解しやすいものにします。
Combine と Async/Await の使用シナリオ
Swiftにおける非同期プログラミングには、CombineとAsync/Awaitの2つの異なるアプローチがあり、それぞれ特定の使用シナリオと利点があります。
Combine
Combineは関数反応型プログラミングフレームワークで、非同期イベントストリームを処理するための一連のオペレーターを提供します。特に複数の非同期イベントを統合、フィルタリング、変換、またはその他の複雑な操作が必要な場合に適しています。
使用シナリオ:
- データストリームの管理: 複数のデータソースを統合、変換、制限などする必要がある場合。
- UIイベントの応答: ユーザー入力やその他のUIイベントをデータストリームに変換して処理する場合。
- リアクティブプログラミング: リアクティブプログラミングモデルを必要とする場合、Combineが理想的です。
- 長期的なサブスクリプション: イベントやデータの変化を長期間監視する必要がある場合、Combineは便利なサブスクリプションメカニズムを提供します。
Async/Await
Async/AwaitはSwift 5.5で導入されたもので、非同期プログラミングをより直感的で簡潔にし、コードを同期コードのように見せることができます。このアプローチは、複雑なデータストリーム処理が不要な単一または少数の非同期操作を簡略化するのに特に適しています。
使用シナリオ:
- 単純な非同期タスク: ネットワークからのデータ読み込みなど、単一または少数の非同期タスクに対して、async/awaitはより明快で簡潔な方法を提供します。
- 構造化された並行処理: 複数の関連する非同期タスクを処理する場合にasync/awaitがサポートする構造化された並行処理が役立ちます。
- エラー処理: async/awaitを使用すると、標準のエラー処理パターンを使用してコードをより簡単に維持できます。
- 同期コードスタイル: 同期コードスタイルで非同期操作を扱いたい場合、async/awaitが適しています。
完全な置換について
Async/Awaitは多くのシナリオでCombineを置き換えることができますが、Combineを完全に置き換えることはできません。特に、複雑なイベントストリームやリアクティブプログラミングモデルを扱う場合、Combineには独自の利点があります。
Combineが必要な場合
持続的なデータストリームやイベントストリームを扱い、複雑な変換、統合、その他の操作が必要な場合、Combineが適しています。例えば、リアルタイムのデータソース(ユーザーインターフェースのイベントや外部デバイスからの入力など)を処理し、これらのデータに対して複雑な処理や複数のデータストリーム間の相互作用が必要な場合、Combineがより適切なツールです。
結局のところ、どちらのツールを選択するかは、具体的なアプリケーションのシナリオとあなたのプログラミングスタイルに依存します。場合によっては、両者を組み合わせて使用し、それぞれの利点を最大限に活用することもあります。