[RxSwift] Variable -> Observer への単方向バインディングをシンプルに書く


最近、RxSwiftを始めました。（ので、まだ初心者です）

いろいろとコードを書きながら勉強中なのですが、`Variable`から`Observer`へ単方向バインディングするコードが少し長いなぁ、と感じたので改善策を考えてみました。

# 基本な書き方

以下のようなViewModelがあって。

```swift
class ArticleViewModel {
    var title = Variable<String>("Titleですよ")
    var content = Variable<String>("Contentですよ")
}
```

以下のようなViewControllerがあるとします。

```swift
class ArticleViewController: UIViewController {

    @IBOutlet weak var articleTitleLabel: UILabel!
    @IBOutlet weak var articleContentLabel: UILabel!
    
    let viewModel = ArticleViewModel()
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        
        // Title
        viewModel.title
            .asObservable()
            .bindTo(articleTitleLabel.rx_text)
            .addDisposableTo(disposeBag)
        
        // Content
        viewModel.content
            .asObservable()
            .bindTo(articleContentLabel.rx_text)
            .addDisposableTo(disposeBag)
    }
}
```

`title`プロパティについて見てみると、

1. `Variable<String>`型である`title`を、
2. `.asObservable()`で`Observable<String>`に変換して、
3. `.bindTo()`で`UILabel`の`.rx_text`プロパティに単方向バインディングし
4. `.addDisposableTo()`でメモリ管理をしています。

いわゆる「`Observable<T>`と`Observer<T>`は接続可能である」というRxの特徴を端的に表したコードかと思います。

しかし、今回はプロパティが2つだけですが、これが増えてくると一気に可読性が悪くなる、というよりも **ボイラープレートなコードが多すぎる** ような気がしてきます。

# コンセプト

`map`や`filter`などのオペレーターを途中に挟んで加工することもあると思うので一概には言えませんが、例えばコンセプトとして以下のように書けたらスッキリするのではないでしょうか？

```swift
viewModel.title   >>= articleTitleLabel.rx_text   // Title
viewModel.content >>= articleContentLabel.rx_text // Content
```

実際にはこのように書くことは出来ないのですが、このコードは本当にやりたいことの本質、つまり「`viewModel.title`と`articleTitleLabel.rx_text`を単純にバインドしたい」だけを過不足無く表現できています。

（`>>=`は、Haskellにおけるバインドなのですが、この演算子にこだわる理由はありません）

# 実装

コンセプトは良いと思うのですが、残念ながらSwiftでは前述したようなコードを書くことが（たぶん）出来ません。

`>>=`を演算子としてオーバーライドすることは出来ますが、メモリ管理用の`disposeBag`を`addDisposableTo()`に渡すためには、クラス内のインスタンスメソッドとして宣言する必要がありますが、Swiftでは演算子をクラス内に定義することは出来ません。

ということで、演算子は諦めてインスタンスメソッドで書いてみます。

```swift
func bind<T>(variable: Variable<T>, _ observer: AnyObserver<T>) {
    variable
        .asObservable()
        .bindTo(observer)
        .addDisposableTo(disposeBag)
}
```

すると以下のように書けるようになります。

```swift
bind(viewModel.title,   articleTitleLabel.rx_text)   // Text
bind(viewModel.content, articleContentLabel.rx_text) // Content
```

演算子ほどではありませんが、かなり短かくなりましたし、余計な視覚ノイズが少なくなってスッキリして可読性も良いように感じます。

# 共通化

ところで、さきほどの`bind()`メソッドは利用したいクラス毎に定義し直す必要があるのでしょうか？

まさか。

Swiftには`Protocol Extensions`という仕組みがあります。これで共通化してみます。

```swift
protocol RxManaged {
    var disposeBag: DisposeBag { get }
}

extension RxManaged {
    func bind<T>(variable: Variable<T>, _ observer: AnyObserver<T>) {
        variable
            .asObservable()
            .bindTo(observer)
            .addDisposableTo(disposeBag)
    }
}
```

最終的なコードは以下のようになりました。

```swift
class ArticleViewController: UIViewController, RxManaged {

    @IBOutlet weak var articleTitleLabel: UILabel!
    @IBOutlet weak var articleContentLabel: UILabel!
    
    let viewModel = ArticleViewModel()
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        bind(viewModel.title,   articleTitleLabel.rx_text)   // Text
        bind(viewModel.content, articleContentLabel.rx_text) // Content        
    }
}
```

# 最後に

この記事は特定の領域のみにしかフォーカスしていません。実際の問題はもっと複雑なケースが多いでしょう。

しかし、`RxSwift`にから標準で提供された機能だけをそのまま利用するのではなく、共通コードを書くことでコードの見通しを良くすることが出来るのではないかと思いました。

というわけで、RxSwiftを勉強し始めて「同じような疑問・不満を持つ人もいるかも？」と思って記事にしてみましたが、もし良いやり方をご存知の方がいましたら是非教えて下さい。