はじめに
こんにちは。グノシー事業部でiOSの開発を担当している hongmhoonです。
この記事はGunosy Advent Calendar 2018の11日目の記事です。 昨日はtoshimaruさんのCircleCI 2.0/2.1の機能をフル活用してCI/CDワークフローを改善してみたでした。
Notificationの基本
NotificationCenter.default.post(name: .blogDeadlineDidCome, object: nil)
iOSで何かのイベントをアプリ全体的に送信するには上のように書くのが基本です。
こちらは同期的に処理されますが、呼出元の処理が遅延するなどの理由で送信のタイミングをずらしたい時もあります。
Notificationの非同期送信
非同期でNotificationを送信するにはNotificationQueue にNotificationをenqueueします。
以下のコードはasyncNotification1を先にNotificationQueue にenqueueしてからsyncNotificationをNotificationCenterで送信してますが、出力を見るとSyncNotificationが先に同期で処理されてAsyncNotification1は呼出メソッドの処理が終わった後非同期で処理されます。
全コードはここにあります。
func postNotification() { print("postNotification start") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle) print("asyncNotification1 did post") print("syncNotification will post") NotificationCenter.default.post(name: .syncNotification, object: nil) print("syncNotification did post") print("postNotification end") } // 出力 postNotification start asyncNotification1 will post asyncNotification1 did post syncNotification will post NSNotificationName(_rawValue: SyncNotification) syncNotification did post postNotification end NSNotificationName(_rawValue: AsyncNotification1)
Notificationの送信タイミング設定
NotificationQueue のenqueueメソッドにはpostingStyleパラメータがありこれで送信タイミングの設定が可能です。
- .now : 同期送信
- .asap : 呼出メソッドの処理が終わった直後に送信
- .whenIdle : run loopが暇になったら送信
以下のコードは.whenIdle -> .asap -> .now の順にenqueueしましたが、.now は同期で処理されて.whenIdleは.asapより遅く処理されます。
func postNotification() { print("postNotification start") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle) print("asyncNotification1 did post") print("asyncNotification2 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification2), postingStyle: .asap) print("asyncNotification2 did post") print("asyncNotification3 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification3), postingStyle: .now) print("asyncNotification3 did post") print("syncNotification will post") NotificationCenter.default.post(name: .syncNotification, object: nil) print("syncNotification did post") print("postNotification end") } // 出力 postNotification start asyncNotification1 will post asyncNotification1 did post asyncNotification2 will post asyncNotification2 did post asyncNotification3 will post NSNotificationName(_rawValue: AsyncNotification3) asyncNotification3 did post syncNotification will post NSNotificationName(_rawValue: SyncNotification) syncNotification did post postNotification end NSNotificationName(_rawValue: AsyncNotification2) NSNotificationName(_rawValue: AsyncNotification1)
Notificationの重複除外
NotificationQueue のenqueueメソッドにはcoalesceMaskパラメータがありこれで重複除外の設定が可能です。
- .none : 重複除外しない
- .onName : 同じ名前のNotificationは除外
- .onSender : 同じSenderのNotificationは除外
onName
以下のコードはasyncNotification1とasyncNotification2を2回ずつenqueueしましたがcoalesceMask: .onNameを使ったasyncNotification1は一回のみ処理されます。
func postNotification() { print("postNotification start") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle) print("asyncNotification1 did post") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle, coalesceMask: .onName, forModes: nil) print("asyncNotification1 did post") print("asyncNotification2 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification2), postingStyle: .whenIdle) print("asyncNotification2 did post") print("asyncNotification2 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification2), postingStyle: .whenIdle, coalesceMask: .none, forModes: nil) print("asyncNotification2 did post") print("postNotification end") } // 出力 postNotification start asyncNotification1 will post asyncNotification1 did post asyncNotification1 will post asyncNotification1 did post asyncNotification2 will post asyncNotification2 did post asyncNotification2 will post asyncNotification2 did post postNotification end NSNotificationName(_rawValue: AsyncNotification1) NSNotificationName(_rawValue: AsyncNotification2) NSNotificationName(_rawValue: AsyncNotification2)
onSender
以下のコードはasyncNotification1を3回enqueueしましたがcoalesceMask: .onSenderの影響で一回のみ処理されます。
func postNotification() { print("postNotification start") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle) print("asyncNotification1 did post") print("asyncNotification2 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification2), postingStyle: .whenIdle) print("asyncNotification2 did post") print("asyncNotification3 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification3), postingStyle: .whenIdle, coalesceMask: .onSender, forModes: nil) print("asyncNotification3 did post") print("postNotification end") } // 出力 postNotification start asyncNotification1 will post asyncNotification1 did post asyncNotification2 will post asyncNotification2 did post asyncNotification3 will post asyncNotification3 did post postNotification end NSNotificationName(_rawValue: AsyncNotification1)
Enqueue済みNotificationの削除
NotificationQueue のdequeueNotificationsメソッドに削除したいNotificationを設定することで送信前のNotificationの削除が可能です。
以下のコードはasyncNotification1とasyncNotification2をenqueueしましたがdequeueされたasyncNotification1は処理されず、asyncNotification2のみ処理されます。
func postNotification() { print("postNotification start") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle) print("asyncNotification1 did post") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification2), postingStyle: .whenIdle) print("asyncNotification1 did post") NotificationQueue.default.dequeueNotifications(matching: .init(name: .asyncNotification1), coalesceMask: Int(NotificationQueue.NotificationCoalescing.onName.rawValue)) print("postNotification end") } // 出力 postNotification start asyncNotification1 will post asyncNotification1 did post asyncNotification1 will post asyncNotification1 did post postNotification end NSNotificationName(_rawValue: AsyncNotification2)
終わりに
端末の性能向上やRecativeなこの頃なのであまり使う機会はないかも知らないですが参考になれたら幸いです。
明日はiOSネタではなくて、技術ブログの始め方になる予定なので、引き続きGunosy Advent Calendar 2018をお楽しみください。
参考
サンプルコード
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(observeSyncNotification(notification:)), name: .syncNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(observeAsyncNotification1(notification:)), name: .asyncNotification1, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(observeAsyncNotification2(notification:)), name: .asyncNotification2, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(observeAsyncNotification3(notification:)), name: .asyncNotification3, object: nil) postNotification() } func postNotification() { print("postNotification start") print("asyncNotification1 will post") NotificationQueue.default.enqueue(.init(name: .asyncNotification1), postingStyle: .whenIdle) print("asyncNotification1 did post") print("syncNotification will post") NotificationCenter.default.post(name: .syncNotification, object: nil) print("syncNotification did post") print("postNotification end") } @objc func observeSyncNotification(notification: Notification) { print(notification.name) } @objc func observeAsyncNotification1(notification: Notification) { print(notification.name) } @objc func observeAsyncNotification2(notification: Notification) { print(notification.name) } @objc func observeAsyncNotification3(notification: Notification) { print(notification.name) } } extension Notification.Name { public static let syncNotification = Notification.Name("SyncNotification") public static let asyncNotification1 = Notification.Name("AsyncNotification1") public static let asyncNotification2 = Notification.Name("AsyncNotification2") public static let asyncNotification3 = Notification.Name("AsyncNotification3") }