この記事は Sansan Advent Calendar 2015の17日目の記事です。
RxJavaにおいてシンプルにSubscriptionのunsubscribe()を行うためのライブラリSubskriptionというライブラリを作りましたので、その紹介をしたいと思います。
解決したい問題
これまで、RxJavaを用いて非同期処理などを行う場合は、非同期処理が完了する前に利用する側のクラスが破棄される場合など、適切にその処理をunsubscribe()する必要がありました。
そのためにCompositeSubscription
がよく使用されます。
例えばAndroidのActivityでは通信中に画面遷移が起こり、画面上のObjectが破棄されることがあるため、onPause()
などでunsubscribe()する必要があります。このボイラープレート的に必要となるCompositeSubscription
の定義がRxJavaをAndroid等で扱う場合に厄介になります。
class SomeActivity : Activity() {
var compositeSubscription = CompositeSubscription()
fun onClick() {
val subscription = loadData() // 非同期でデータを取得
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// Do something
}
compositeSubScription.add(subscription)
}
override fun onPause() {
super.onPause()
compositeSubscription.unsubscribe()
// CompositeSubscriptionは一度unsubscribe()すると
// 再度subscriptionの追加ができなくなるので
// 新しいインスタンスを作りなおす
compositeSubscription = CompositeSubscription()
}
}
Androidではこの問題を解決するためのライブラリとして、RxLifeCycleがありますが、RxLifeCycleを使う場合はActivity側でRxActivityクラスを継承しなければならず、他のActivityクラスを継承したい場合などには使えません。
こんなときにCompositeSubscriptionにまつわるボイラープレートの問題を解決してくれるのがこのライブラリです。
インストール
guild.gradleで以下のように指定してください。
dependencies {
compile 'com.github.yamamotoj:subskription:0.2.1'
}
repositories {
maven { url "https://dl.bintray.com/yamamotoj/maven" }
}
使い方
Overview
まず、RxJavaを使用するSomeActiviry
クラスでは
AutoUnsubscribable by AutoUnsubscribableDelegate()
のように記述し、AutoUnsubscribable
インターフェイスをAutoUnsubscribableDelegate
のclass delegateを使って実装するようにします。
そうすることで、AutoUnsubscribableDelegate
クラスの内部でCompositeSubscriptionが保持されるため、SomeActivity
クラスでCompositeSubscription
を定義する必要はありません。
そして、Observable<T>
の拡張関数であるautoUnsubscribe(key:Any)
をsubscribe時に呼んでおくことで、同じkey
に合致するunsubscribe(key:Any)
の呼び出しタイミングで登録されたsubscriptionがunsubscribe()
されます。
class SomeActivity : Activity(), AutoUnsubscribable by AutoUnsubscribableDelegate() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun onClick() {
loadData()
// このsubscriptionはunsubscribe("onPause")が呼ばれた時にunsubscribeされる
.autoUnsubscribe("onPause")
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// Do something
}
}
override fun onPause() {
super.onPause()
unsubscribe("onPause")
}
fun loadData(): Observable<String> = Observable.just("")
}
このautoUnsubscribe()
拡張関数はObservable<T>
の他にSubscription
クラスの拡張関数としても定義されているので、以下のような実装が可能です。
fun onClick() {
loadData()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// Do something
}
.autoUnsubscribe("onPause") // subscribe()後にもくっつけられる
}
AutoUnsubscribableの階層構造
AutoUnsubscribableインターフェイスは子要素を持つことができます。AutoUnsubscribableが子要素をもったとき、に親のAutoUnsubscribableのunsubscribe()
を呼ぶと、子要素のunsubscribe()
も呼び出されます。
class Parent: AutoUnsubscribable by AutoUnsubscribableDelegate() {
val child = Child()
init{
// childを子階層として追加する
addAutoUnsubscrivable(child)
}
fun doUnsubscribe(){
// ここでunsubscribe()するとchildオブジェクトのsubscriptionもunsubscribeされる
unsubscribe()
}
}
class Child: AutoUnsubscribable by AutoUnsubscribableDelegate() {
fun onClick() {
loadData()
.autoUnsubscribe()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// Do something
}
}
}
keyありのunsubscribe(key:Any)とkeyなしのunsubscribe()
今までの例でautoUnsubscribe
拡張メソッドとunsubscribe
メソッドには引数にkeyを持つものと、引数を持たないものの2つがあることに気づいた方もいらっしゃるかと思います。
/** keyを指定せずにautoUnsubscribe登録 */
fun <T> Observable<T>.autoUnsubscribe() :Observable<T>
fun Subscription.autoUnsubscribe():Unit
fun unsubscribe():Unit
/** keyを指定してautoUnsubscribe登録 */
fun <T> Observable<T>.autoUnsubscribe(key:Any) :Observable<T>
fun Subscription.autoUnsubscribe(key:Any):Unit
fun unsubscribe(key:Any):Unit
この時、subscriptionを引数keyを指定してautoUnsubscribe(key)
した場合はそれに対応するkeyでunsubscribe(key)
することができます。
それに対して引数なしのunsubscribe()
が呼ばれた時は、keyが指定されたautoUnsubscribe(key)
だろうが引数なしのautoUnsubscribe()
だろうが、すべてのsubscriptionに対してunsubscribe()
が実行されます。また、このunsubscribe()
が呼ばれたAutoUnsubscribable
があるAutoUnsubscribable
の子要素だった場合、unsubscribe()
された子要素は親から切り離されます。もしunsubscribe()
されたAutoUnsubscribable
が子要素を持っていた場合はそれらの子要素すべてにunsubscribe()
を呼び出し、結果的にすべての子要素が切り離されます。
この機能は、例えばListViewのような画面で個々のListItemが何かしらのObservableに対してsubscribeしなければならない状況に有効です。このListItemのSubscriptionがonPause()などのタイミングでunsubscribe()
したい場合は親のunsubscribe()
が伝播されますが、ListItemそのものが破棄される場合は、自分自身でunsubscribe()
することで親要素から切り離され、ListItemそのもののメモリは適切に開放されます。
あとがき
このライブラリの実装に関しての大まかな解説は
この記事はKotlinアドベントカレンダー2015に書いた、KotlinでAndroidのRxJavaをちょっと便利にを参照してください。
ちなみに始めてAndroidのライブラリを公開してmavenから取得できるようにしたのですが、けっこう大変でした。