はじめに
「async
/await
でNotificationCenter
の通知を待機し、時間経過でタイムアウトさせる方法」をご紹介します。
背景
この処理は少し特殊な非同期通信でリクエストに対するレスポンスをNotificationCenter
で受信していました。またレスポンス受信できない場合にアラート表示をするためにタイムアウトを設ける必要がありました。タイムアウト処理は元々DispatchSemaphore
を使って実装していたのですが、この処理をCombineではなくasync
/await
を使って置き換えたいと思って実装しました。
実装方法
以下の2つの方法を組み合わせます。
-
NotificationCenter.Notifications
で通知を待機 -
TaskGroup
でタイムアウト
NotificationCenter.Notifications
で通知を待機
async
/await
でNotificationCenter
を待つのは以下のようなコードで実装します。
NotificationCenter.Notifications
を使用しています。
let notificationName = Notification.Name("hoge")
let notifications = NotificationCenter.default.notifications(named: notificationName, object: nil)
for await notification in notifications {
print(notification)
}
これでNotificationCenter
の通知が来るまで待つことができます。簡単ですね。
マニュアルに記載の通りNotificationCenter.Notifications
はiOS15以降のサポートです。
TaskGroup
でタイムアウト
TaskGroup
とcancelAll
を使うとタイムアウトが実装できます。
こちらの記事のコードを参考にさせていただきました。
2つを組み合わせる
2つを組み合わせたコードが以下です。
@available(iOS 15, *)
func waitWithTimeout(notificationName: NSNotification.Name,
timeoutNanoseconds: UInt64) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
let notifications = NotificationCenter.default.notifications(named: notificationName)
for await _ in notifications {
// レスポンス取得後の処理
return
}
}
group.addTask {
// 指定時間待機後にタイムアウト
try await Task.sleep(nanoseconds: timeoutNanoseconds)
throw CancellationError()
}
let _ = try await group.next()!
group.cancelAll()
}
}
withThrowingTaskGroup
のクロージャでTaskGroup
に2つTask
を追加しています。
1つはNotificationCenter
を待つTask
です。
もう1つは指定時間を待機した後にタイムアウトのthrowを投げるTask
です。
どちらかのTask
が終了したら、もう一方はcancelAll
でキャンセルされます。
以下のような使い方です。
Task {
// 何かしらのリクエストを送信する。
request()
do {
let responseNotificationName = Notification.Name("response")
let timeoutNanoseconds = UInt64(10 * 1_000_000_000)
try await waitWithTimeout(name: responseNotificationName,
timeoutNanoseconds: timeoutNanoseconds)
} catch {
// タイムアウト処理
}
}
まとめ
TaskGroup
とNotificationCenter.Notifications
を組み合わせることで、
NotificationCenter
をasync
/await
で待ちつつ、時間経過するとタイムアウトする処理が書けました。
この方法は以下の要件に合致する場合に有効です。
- iOS15以降サポートであること。
- レスポンスを
NotificationCenter
で受信するケースであること。 - レスポンスにタイムアウトを設ける必要があること。
- Combineでなく
async
/await
を使いたい時。