【SwiftChaining】 NotificationCenterからの通知を受け取る

[SwiftChaining](https://github.com/objective-audio/SwiftChaining)の解説記事その3です。

[SwiftChaining](https://github.com/objective-audio/SwiftChaining)とは何か？というのは、こちらの[最初の記事](https://qiita.com/yasos/items/4d8dd0d1aa63605dc63b)を参考にしてください。

## NotificationCenterからの通知を受け取る

単に値を送信するクラスとして`Notifier`があるというのは[最初の記事](https://qiita.com/yasos/items/4d8dd0d1aa63605dc63b)に書きましたが、自分で何かを通知するならそれで良いとして、`Notifier`は`NotificationCenter`の通知には対応していません。

`NotificationCenter`のイベントを`SwiftChaining`で扱えるようにするために、`NotificationAdapter`というクラスを別に用意しています。コード例としては以下のようになります。

```swift
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`のイベントに変換するクラスです。以下のように生成します。

```swift
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`と同じように書けます。

```swift
let observer = adapter.chain()
    .do { (notification: Notification) in
        print(notification.userInfo!["key"]!)
    }
    .end()
```

イベントで送られてくる値の型は`Notification`ですので、上記のコードのように`do`で受け取れば、普通に`addObserver`した時と同じような感じにクロージャで処理が書けます。

とはいえ`do`を使うだけだと敢えてライブラリを使っている意味がないので、もう少しちゃんとバインディングする感じの使い方を紹介します。

あくまで`userInfo`に必要な値が入っていると言う前提ですが、以下のように`map`で`userInfo`から`String`の値を取り出してイベントの型を変換し、`sendTo`で別のオブジェクトが受け取る感じの書き方ができます。

```swift
// 受け取るオブジェクト
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に対して後から操作する訳でもないのにプロパティを作るのも冗長な感じがするので、解決法を用意しています。それはまた[別の記事](https://qiita.com/yasos/items/ed03468fa6c1510b6d38)で紹介します。