iOS
Swift
RxSwift

Objective-Cで実装したクラスのSwift部分でRxSwiftのDisposeBagを使う

どういう問題か?

タイトルで説明しきれなかったのでここで問題点について説明いたします。

Objective-Cで開発されたプロジェクトを部分的にSwiftに移植していく場合は、Swift側でObjective-Cクラスのextensionとしてメソッドを定義する方法があります。

参考: 【Swift】Objective-Cで書いたクラスをメソッド単位で少しずつSwiftへ移行する方法

勉強会などでiOSアプリエンジニアに話を聞いていると、Objective-CとSwift混在のアプリはまだまだあるのではないかという印象なので、この手法のお世話になっている人もいるのではないでしょうか。

一方、RxSwiftを使っている場合はDisposeBagのインスタンスをUIViewControllerのプロパティとして持たせておいて、ViewController消滅時にDisposeさせる手法が広く扱われていると思います。

しかしSwiftのextension内ではStored propertyを定義することができないため、DisposeBagをViewControllerクラスインスタンスの寿命と連動させることが困難です。

解決方法

Associated Objectを使うことで、SwiftのComputed propertyを用いてもDisposeBagをインスタンスに保持させることができます。Associated Object下記の記事などで解説されているので参照ください。
参考: [Objective-C] Associated Objectを使いランタイムにプロパティを追加する
ざっくりいうと、プロパティとは別の仕組みでオブジェクトに動的にデータを設定するための仕組みです。

とはいえ、こんな黒魔術みたいなのを自分で書きたくないという人にお勧めのライブラリがありました。NSObject+Rx というライブラリを入れることで下記のようにDisposeBagを生成&取得できます

extension HogeViewController {
    func configureBinding() {
        thing
          .bind(to: otherThing)
          .disposed(by: self.rx.disposeBag)
    }
}

self.rx.disposeBag の部分がNSObject+Rxによって追加されたプロパティです。このプロパティは内部でAssociated ObjectとしてDisposeBagのインスタンスを保持するように実装されたComputed propertyです。初回のgetでDisposeBagのインスタンスを生成して保持するようになっています。(2018/05/10現在の情報です。このライブラリの実装は紆余曲折もあったらしく、今後変わる可能性も考えられます)

まとめ

Associated Objectでできます。
ライブラリがすでにあるのでNSObject+Rx を使えます。

NSObject+Rxの目的はこのような目的ではなく、「わざわざDisposeBagを各クラスのプロパティとして定義する手間を省く」ためのものらしいので、今回のような用途の解決策として検索していても見つかりにくいように思いました。検索で見つかる可能性が増えるためにこの記事を書きました。

補足

この記事の内容は下記のブログ記事の内容の一部としても取り上げましたが、記事のタイトルからこの問題の解決を連想しにくいと思いました。そのためこの問題のみを抜き出して記事化しました。

RxSwiftを部分的に導入してみてわかった3つの効果と4つのハマりどころ