RxSwift で循環参照を起こさない書き方を考える

## Binder の独自実装

RxSwift などでデータバインディングする場合、次のようにクロージャを渡すことがあります。

```swift
viewModel.text
    .subscribe(onNext: { [weak label] text in
        label?.text = text
    })
    .disposed(by: disposeBag) 
```

循環参照を起こさないように `weak` でオブジェクトをキャプチャする必要があります。

RxCocoa で提供されている `Binder` を使用すると、循環参照を起こさずに記述できます。
`Observable<String?>` を UILabel にバインディングする場合は、次のように記述します。

```swift
viewModel.text
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)
```

`rx.text` は RxCocoa で次のように実装されています。

```swift
extension Reactive where Base: UILabel {
    public var text: Binder<String?> {
        return Binder(self.base) { label, text in
            label.text = text
        }
    }
}
```

上述の要領で `Binder` を独自に実装することができます。

```swift
extension Reactive where Base: ViewController {
    var text: Binder<String?> {
        return Binder(base) { vc, text in
            vc.label.text = text
        }
    }
}
```

このように `Binder` を実装することで、次のようなメリットがあります。

+ `weak` でオブジェクトをキャプチャするコードがなくなる
+ subscribe 部分からクロージャが消えてコードが簡潔に見えるようになる

## Binder の独自実装による問題

しかし、次のような問題もあると思います。

+ 循環参照を完全に防げていない
+ 本質的ではない実装の量産

### 循環参照を完全に防げていない

次のような実装は、強い参照でキャプチャするので循環参照を引き起こします。

```swift
extension Reactive where Base: ViewController {
    var text: Binder<String?> {
        let vc = base
        return Binder(base) { _, text in // 第一パラメータを使用していない
            vc.setText(text) // vc を weak キャプチャしていない
        }
    }
}
```

`Binder` はクロージャの第一パラメータ使用することで、循環参照を防ぐことができます。
第一パラメータを使用しないのであれば、 `weak` キャプチャする必要があります。

結局、循環参照を防ぐためには、プログラマが実装時に注意を払う必要があるということになります。

### 本質的ではない実装の量産

`Observable` から値を受け取りたいがために、都度 Binder を実装することは、本質的ではない処理を量産していることを意味しています。

Rx を使用していなければ、値を受け取るためのセッターやメソッドがあれば十分だからです。

RxSwift を利用したとたんに、 `subscribe` や `Binder` でクロージャを記述しなければならないことは、Rx を利用するメリットと同時にデメリットを授かっていると言えます。

## 循環参照を起こさない書き方を考える

上述の問題を冗長なコードと捉えて、処理を共通化してみます。
循環参照を防ぐための `weak` キャプチャする処理を共通化して、セッターやメソッドだけ渡して  `subscribe` できるようにします。

### ObservableType にメソッドを追加

RxSwift の `ObservableType` に次のようなメソッドを追加します。

```swift
import RxSwift
import RxCocoa

public extension ObservableType  {

    // セッターでバインド
    func bind<Object: AnyObject>(to object: Object, keyPath: ReferenceWritableKeyPath<Object, E>) -> Disposable {
        return bind { [weak object] element in
            object?[keyPath: keyPath] = element
        }
    }

    // メソッドでバインド
    func bind<Object: AnyObject>(to object: Object, selector: @escaping (Object) -> (E) -> Void) -> Disposable {
        return bind { [weak object] element in
            object.map(selector)?(element)
        }
    }
}
```

このような拡張をすると、`subscribe` 時にセッターやメソッドだけを渡すことができます。
循環参照も起きません。
次のようにして使います。

### セッターでバインド

```swift
class ViewController: UIViewController {
    ....
    var text: String?
    ....
    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.text
            .bind(to: self, keyPath: \.text)
            .disposed(by: disposeBag)
    }
```

### メソッドでバインド

```swift
class ViewController: UIViewController {
    ....
    func setText(_ text: String?) {
    }
    ....
    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.text
            .bind(to: self, selector: ViewController.setText)
            .disposed(by: disposeBag)
    }
```

`ViewController.setText` のように `(クラス名).(インスタンスメソッド)` のような記述をした場合は、インスタンスメソッドではなく、カリー化された関数になります。
上述の例の `setText` の場合は次のような型の関数です。

```swift
let foo: (ViewController) -> (String?) -> () = ViewController.setText
```

`ViewController.setText` 自体は特定の `ViewController` オブジェクトとは無関係な関数です。
`bind(to: ...` で渡している具体的なオブジェクトを内部の実装で weak キャプチャしているので循環参照は発生しません。

`ReferenceWritableKeyPath` についても同様に、特定のオブジェクトとは無関係ですので循環参照を回避することができます。