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に対して後から操作する訳でもないのにプロパティを作るのも冗長な感じがするので、解決法を用意しています。それはまた別の記事で紹介します。