0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

async/awaitでNotificationCenter通知をタイムアウト付き待機する方法

Last updated at Posted at 2023-10-06

はじめに

async/awaitNotificationCenterの通知を待機し、時間経過でタイムアウトさせる方法」をご紹介します。

背景

この処理は少し特殊な非同期通信でリクエストに対するレスポンスをNotificationCenterで受信していました。またレスポンス受信できない場合にアラート表示をするためにタイムアウトを設ける必要がありました。タイムアウト処理は元々DispatchSemaphoreを使って実装していたのですが、この処理をCombineではなくasync/awaitを使って置き換えたいと思って実装しました。

実装方法

以下の2つの方法を組み合わせます。

  • NotificationCenter.Notificationsで通知を待機
  • TaskGroupでタイムアウト

NotificationCenter.Notificationsで通知を待機

async/awaitNotificationCenterを待つのは以下のようなコードで実装します。
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でタイムアウト

TaskGroupcancelAllを使うとタイムアウトが実装できます。
こちらの記事のコードを参考にさせていただきました。

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 {
        // タイムアウト処理
    }
}

まとめ

TaskGroupNotificationCenter.Notificationsを組み合わせることで、
NotificationCenterasync/awaitで待ちつつ、時間経過するとタイムアウトする処理が書けました。

この方法は以下の要件に合致する場合に有効です。

  • iOS15以降サポートであること。
  • レスポンスをNotificationCenterで受信するケースであること。
  • レスポンスにタイムアウトを設ける必要があること。
  • Combineでなくasync/awaitを使いたい時。
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?