はじめに
こんにちは。グノシー事業部で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") }