Swift4 KVOを使ってRxSwiftみたいにDataBindingする

Swift4でKVOが使いやすくなったのは知ってたのですが、結局触らずに1年以上たってしまいました。
最近になりiOSエンジニアとして再び働くことになったため、勉強がてらに少し触ってみようと、個人開発のアプリでKVOを使ったDataBindingを試してみたのでアウトプットします。

#### 使用例

``` .swift
@objcMembers class ViewModel: NSObject {
    private (set) dynamic var count = 0
    private (set) dynamic var title: String? = ""
}
```

``` .swift
    private var bag = DisposableBag()
    private let viewModel = ViewModel()
    private let label = UILabel()

    func bindToViewModel() {

        viewModel.subscribe(\.count) { [weak self] count in
            self?.label.text = "count: \(count)"
        }.dispose(by: bag)
        
        viewModel
            .bind(\.title, to: label, at: \.text)
            .dispose(by: bag)
    }
```

#### Swift4のKVO

まず普通にSwift４のKVOを使ってViewModelの値を監視してみます。

参考 [Swift4のKVOに感動した。](https://qiita.com/ObuchiYuki/items/d00ce5f44725672184da)


``` .swift
final class ViewModel: NSObject {
    @objc private (set) dynamic var count = 0
}
```


``` .swift
final class ViewController: UIViewController {

    private var observations: [NSKeyValueObservation] = []
    private let viewModel = ViewModel()
    private let countLabel = UILabel()

    func bindToViewModel() {
        let countObservations = viewModel.observe(\.count, options: .new) { [weak self] _, change in
            guard let `self` = self, let newValue = change.newValue else {
                return
            }
            self.countLabel.text = "count: \(count)"
        }
        observations.append(countObservations)
    }
}
```

結構スッキリかけるようになったんですね。
ただ、NSObjectを継承したりdynamic修飾子を使わないといけないのはしょうがないとしても
もう少し簡潔にかけたら嬉しいと思います。


#### Extensionを実装してみる

``` .swift

extension NSObjectProtocol where Self: NSObject {

    func observe<Value>(_ keyPath: KeyPath<Self, Value>, changeHandler: @escaping (Value) -> ()) -> NSKeyValueObservation {
        return observe(keyPath, options: .new) { _, change in
            guard let newValue = change.newValue else { return }
            changeHandler(newValue)
        }
    }
}
```


``` .swift
    func bindToViewModel() {
        let countObservations = viewModel.observe(\.count) { [weak self] count in
            self?.countLabel.text = "count: \(count)"
        }
        observations.append(countObservations)
    }
```

optionの引数とオプショナルバインディングがなくなり少しスッキリしました。


#### DisposableBagクラスを追加してインターフェースをRxSwiftみたいにする

ただ監視をつずけるためにはobservationを保持しないといけないので少し冗長に感じます。
DisposableBagクラスを作成して、こいつに保持してもらうようにします。

``` .swift
final class DisposableBag {

    private var observations: [NSKeyValueObservation] = []

    func append(_ observation: NSKeyValueObservation) {
        observations.append(observation)
    }
}
```

次にNSKeyValueObservationにDisposableプロトコルをつけて
DisposableBagに追加できるようにします。

``` .swift
protocol Disposable: class {
    func dispose(by bag: DisposableBag)
}


extension NSKeyValueObservation: Disposable {

    func dispose(by bag: DisposableBag) {
        bag.append(self)
    }
}
```

戻り値をDisposableに修正します。ついでにメソッド名もsubscribeに変えてみます

``` .swift

extension NSObjectProtocol where Self: NSObject {

    func subscribe<Value>(_ keyPath: KeyPath<Self, Value>, changeHandler: @escaping (Value) -> ()) -> Disposable {
        return observe(keyPath, options: [.new]) { _, change in
            guard let newValue = change.newValue else { return }
            changeHandler(newValue)
        }
    }
}
```



``` .swift
  
    private var bag = DisposableBag()
    private let viewModel = ViewModel()

    func bindToViewModel() {
        viewModel.subscribe(\.count) { [weak self] count in
            self?.label.text = "count: \(count)"
        }.dispose(by: bag)
    }

```

RxSwiftぽくなったと思います。


#### bindメソッドを実装してみる。

``` .swift

extension NSObjectProtocol where Self: NSObject {
    
    func bind<Target, Value>(_ fromKeyPath: KeyPath<Self, Value>,
                                        to target: Target,
                                        at targetKeyPath: ReferenceWritableKeyPath<Target, Value>
        )  -> Disposable {
        
        return subscribe(fromKeyPath) { newValue in
            target[keyPath: targetKeyPath] = newValue
        }
    }
}
```


``` ViewModel.swift

final class ViewModel: NSObject {
    @objc private (set) dynamic var bindString: String? = "" 
}
```

``` ViewController.swift
    private var bag = DisposableBag()
    private let viewModel = ViewModel()
    private let label = UILabel()

    func bindToViewModel() {
        viewModel
            .bind(\.bindString, to: label, at: \.text)
            .dispose(by: bag)
    }
```

こんな感じで一応bindすることができました。ただオプショナルつけないと`Type of expression is ambiguous without more context`ってなってビルド通らなくて微妙でした。軽く調べた感じSwiftのバグとか出たけど良くわかりませんでした。



#### 終わりに

ちょこっとExtension書くだけでKVOを使ってRxSwiftっぽくDataBindingできるようになった？と思います。
一応最終的なコードをGistにのっけておきます。変更箇所が何点かあります。
https://gist.github.com/churabou/21dc2500fd8b7e6956e06abfe6e5d124