SwiftChainingの解説記事その3です。
SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。
NotificationCenterからの通知を受け取る
単に値を送信するクラスとしてNotifierがあるというのは最初の記事に書きましたが、自分で何かを通知するならそれで良いとして、NotifierはNotificationCenterの通知には対応していません。
NotificationCenterのイベントをSwiftChainingで扱えるようにするために、NotificationAdapterというクラスを別に用意しています。コード例としては以下のようになります。
import UIKit
import Chaining
class MyClass {
static let name = Notification.Name("MyClassNotificationName")
func post(value: String) {
NotificationCenter.default.post(name: MyClass.name,
object: self,
userInfo: ["key": value])
}
}
let object = MyClass()
let adapter = NotificationAdapter(MyClass.name,
object: object,
notificationCenter: .default)
let observer = adapter.chain()
.do { (notification: Notification) in
// userInfo["key"]の値をログに出力
print(notification.userInfo!["key"]!)
}
.end()
// userInfo["key"]に"test"を入れて通知を送り、"test"がログに出力される
object.post(value: "test")
解説
NotificationAdapterは、NotificationCenterから通知されたイベントをSwiftChainingのイベントに変換するクラスです。以下のように生成します。
let adapter = NotificationAdapter(MyClass.name,
object: object,
notificationCenter: .default)
ひとつめの引数には通知を受け取りたいNotification.Nameを渡し、2つめのobjectには送信元のインスタンスを渡します。この辺りはNotificationCenterでaddObserverを呼ぶ場合と同じ意味です。3つめにはNotificationCenterを渡せますが、通常は.defaultで良いでしょう。
なお、objectとnotificationCenterは省略できます。省略すると、objectはnilになりどのオブジェクトからも関係なく受け取りますし、notificationCenterを省略したら.defaultになります。
NotificationAdapterにもchain()関数があってバインディング処理が書けますので、通知を受け取った時の処理はNotifierと同じように書けます。
let observer = adapter.chain()
.do { (notification: Notification) in
print(notification.userInfo!["key"]!)
}
.end()
イベントで送られてくる値の型はNotificationですので、上記のコードのようにdoで受け取れば、普通にaddObserverした時と同じような感じにクロージャで処理が書けます。
とはいえdoを使うだけだと敢えてライブラリを使っている意味がないので、もう少しちゃんとバインディングする感じの使い方を紹介します。
あくまでuserInfoに必要な値が入っていると言う前提ですが、以下のようにmapでuserInfoからStringの値を取り出してイベントの型を変換し、sendToで別のオブジェクトが受け取る感じの書き方ができます。
// 受け取るオブジェクト
let receiver = ValueHolder<String>("")
let observer = adapter.chain()
.map { (notification: Notification) -> String? in
return notification.userInfo?["key"] as? String
}
.sendTo(receiver)
.end()
object.post(value: "test")
// receiverの値は"test"になっている
print(receiver.value)
sendToが受け取れる型
ちなみに、このコードを見て違和感を感じた方がいるかもしれません。値を受け取るValueHolderの型はStringなのに、mapで変換して送る値の型はオプショナルのString?で型が違っています。
sendTo関数はオプショナルの有無の違いであれば、値を送れるようにしています。今回のコードのようにオプショナルから非オプショナルの場合だと、nilが来たら無視するようになっています。
Adapterの保持
上記のサンプルコードではNotificationAdapterをlet adapterでローカルで保持しているだけなのですが、実際のアプリでは監視が必要な間はどこかしらのプロパティに保持する必要があります。NotificatoinAdapterが破棄されてしまうとNotificationCenterの監視が外れてしまうからです。
そのあたりを意識せずに書いていると、インスタンスが作られたそばから解放されてて動いていないという事が起こってしまいます。
とは言えAdapterに対して後から操作する訳でもないのにプロパティを作るのも冗長な感じがするので、解決法を用意しています。それはまた別の記事で紹介します。