前回は質問タイムでしたので、今回は改めてその回で予定していた、ひと通り見終えた感じの個人的にお気に入りな技術ブログ「その Swift コード、こう書き換えてみないか」の余興的に続く事項の最後のところから、 NotificationCenter
を Swift Concurrency で扱うまわりを見ていく回にしますね。どうぞよろしくお願いします。
——————————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #282
00:00 開始 00:35 今日は NotificationCenter を Concurrency で扱う話 01:05 見たかったページが見つからない ⋯ 04:40 NotificationCenter のおさらい 07:59 NotificationCenter を Concurrency で表現する 09:14 NSWorkspace の notificationCenter 11:09 AsyncSequence を使って通知を待つ方法 14:06 Notifications を実際に使って試してみる 15:01 イニシャライザーの意味合いの違い 18:17 Notifications は通知を待ち続ける 20:08 無限ループでもスリープは必要なさそう 23:11 CheckedContinuation を活用する方法 26:12 クロージング ———————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #282
さて、始めていきますね。今日は前回予定していたお話を進めます。具体的には、ノーティフィケーションセンターをSwiftコンカレンシーで使うという内容です。まず、ファンデーションのノーティフィケーションセンターをコンカレンシーで使用する方法についてお話します。
これに関連しているブログ記事があり、今日はそれを参考にしながら進めます。ただ、公式サイトも参考にする必要がありますので、そちらも合わせて確認しますね。しかし、田中亮さんのブログがどこにあるのか見つからないようです。再度リロードしてみましょう。
どうやら見つからないようなので、手元の履歴から探すことにします。履歴には残っているかもしれません。少々お待ちを……どうも見当たりませんね。仕方ないのでGoogleで「ノーティフィケーションセンター コンカレンシー」で検索してみます。
例えば、NotificationCenter
とコンカレンシーを組み合わせる方法ですが、使い方はさまざまあります。ただ、あまりにも多様で、まだ完全に使い慣れていないので丁寧に調べていきたいと思います。
ここでポイントとなるのは、現代的なSwiftコンカレンシーをうまく使いこなすことです。適当に探していきましょう。田中龍馬さんのブログにあったはずですので、それを手がかりに話を進めていきたいのですが、今のところ見つかりません。
次に進みます。NotificationCenter
を使う際の注意点などについても説明していきたいところです。どうしても見つからない場合は、自分で記述しながら進めることになりますが、もう少し探してみます。
検索の仕方が悪いのかもしれません。適当に進めますね。
以上が今回の内容です。それでは、具体的なコード例を次回ご紹介します。それが今回の結論とさせていただきます。 ということで、まずノーティフィケーションセンターについて説明します。ここにいる人はみんな知っているかもしれませんが、フレームワークの一般的に使われる通知のことです。コンバインのAPIから変換があるんだ、なるほど。コンバインについては全然やっていないんですが、ファンデーションはここではフレームワークよりもローレベルで使用します。
システムがノーティフィケーションセンターに対して最初に用意している通知は、例えば「名前付き」で通知を行い、その通知をオブザーブする(監視する)ことができます。特定のイベントが発生すると、ノーティフィケーションセンターに対して通知が送られ、この通知を受け取ることができます。たとえば、アプリケーションが起動したときにはNSWorkspace
のアプリケーションノーティフィケーションが発火します。
さらに、この通知はobject
や必要に応じてuserInfo
などで追加情報を持たせて送信します。また、オブザーバーを登録しておくことで、通知がポストされたときに実行されるブロックをあらかじめ設定しておきます。これがクロージャーパターンですが、これを使えばストックコンカレンシーで通知を表現できるようになります。
通常、別のスレッドやイベントでオブザーバーを初期化しておき、特定のアクションが発生したときに通知がポストされるように設定します。たとえば、ボタンが押されたときに通知をポストするなどです。アプリ全体で共通的に利用されることが多いです。
NSWorkspace
を使用するにはAppKit
が必要です。これはNSWorkspace
のshared
インスタンスに対して操作を行う際に重要です。NSWorkspace
のshared
のノーティフィケーションセンターは、例えばマックアプリケーションがスリープから復帰するなどのイベントをキャッチするのに使われます。iOSのノーティフィケーションセンターとは微妙に異なります。
まとめると、基本的にはiOSの知識で対応可能ですが、NSWorkspace
など特定のケースが必要な場合には注意が必要です。どちらもシングルトンであり、一つのノーティフィケーションセンターを使ってオブザーバーとポストを操作します。こうすることでプロジェクト全体で効率的に通知を管理できます。
他に方法があるかどうかについても調査が必要で、例えば特定のブログに記載されている方法なども参考にすることができます。このようにして、ノーティフィケーションセンターを使ったプログラムを効果的に設計することが重要です。 一番簡単な方法として挙げられるのが、「NotificationCenter」に面白いメソッドが用意されていることです。この「NotificationCenter」のメソッドですが、例えば、NSWorkspace
の didLaunchApplicationNotification
のように使うと、このメソッドが戻り値として NotificationCenter
を返します。この NotificationCenter
が返すのは、非同期なシーケンスです。
NotificationCenterの定義を見ると、これは非同期シーケンスになっていて、通知が来るたびにシーケンスで情報が届く仕組みになっています。次に、この通知を受け取るための例を示します。
たとえば、次のように NotificationCenter
を使います。
NotificationCenter.default.publisher(for: .myNotification)
.sink { notification in
// 通知を受け取ったときの処理
}
.store(in: &cancellables)
ここで、Notification.Name
を定義して使う方法もあります。例えば、次のようにします。
extension Notification.Name {
static let myNotification = Notification.Name("myNotification")
}
このようにしておくと、例えば以下のようにして通知をポストすることができます。
NotificationCenter.default.post(name: .myNotification, object: nil)
次に、NotificationCenter
を使って通知を受け取る側のコードを示します。以下のように combine
を使って、通知を受け取ることができます。
import Combine
var cancellables = Set<AnyCancellable>()
NotificationCenter.default.publisher(for: .myNotification)
.sink { notification in
print("Notification received!")
}
.store(in: &cancellables)
この例では、受け取った通知に対して print
で "Notification received!" と出力しています。cancellables
は通知の受け取りを管理するために使います。
このように NotificationCenter
を使うことで、簡単に通知の受け取りと送信ができます。実際にコードを動かして確認するといいでしょう。
さらに、イニシャライザーについても議論しました。それぞれのイニシャライザーが何を意図しているのか、どのように使うのかを理解することが重要です。特に、RawRepresentable
に関連するイニシャライザーの使い方や、そのプロトコルに準拠するための実装についても理解しておくと良いでしょう。 それにつけて、ノーティフィケーションセンターの通知シーケンスはおそらく永続的に続くのかという質問があります。基本的には止められることはないと思いますが、もしノーティフィケーションセンターがキャンセルされれば、何か起こるかもしれません。しかし、通常は通知が永遠と続く感じです。この場合、無限ループになる感じですね。
無限ループになるときには、async
や await
を適切に使用することで、タスクのブロックを避けることができます。ただ、具体的な知識がある方がいれば教えてほしいですが、基本的には await
がある限り問題ないように思います。一般的な無限ループの場合には、タスクを他に譲るために何かしらの処理を入れる必要がありますが、このケースでは特に問題はないでしょう。
無限ループがよく嫌われる理由の一つは、複数の理由からタスクをブロックしてしまうためです。そのため、通常はスリープを入れたりしてループの頻度を制御することがあります。しかし、await
が入っている場合は、特にスリープを入れる必要もなく、適切に処理がなされます。昔はスレッドスリープを入れないと、パフォーマンスが落ちるといった話もありましたが、今は適切に対応できるようになっています。
無限ループ自体は推奨はされませんが、必要な場面では問題なく使えると思います。必ずしもスリープを入れる必要はなく、ケースバイケースで対応することが重要です。例えば、通知センターを非同期で使用する場合、特にスリープを入れずに実装しても問題ないでしょう。
ノーティフィケーションセンターのオブザーブがデリゲートパターンのように機能します。これにより、どこかのタイミングで関数を非同期で用意して、それに対する処理を行うことができます。具体的には、コンティニュエーションをプロパティに保持して、通知を受け取ったタイミングでそのコンティニュエーションを再開させる流れです。
例として、以下のような感じです。
var continuation: CheckedContinuation<Void, Never>?
NotificationCenter.default.addObserver(forName: .someNotification, object: nil, queue: nil) { notification in
continuation?.resume()
}
func someAsyncFunction() async {
await withCheckedContinuation { cont in
continuation = cont
}
}
この考え方を応用して、適切な場面で await
やwithContinuation
を使い分けていきます。
今日は以上の内容で時間になりましたので、この辺で終わりにします。コメントなどいただければ、次回の勉強会でさらに詳しく説明するかもしれません。それでは、お疲れ様でした。ありがとうございました。