Subskription - KotlinでRxJavaを便利にするライブラリ


この記事は [Sansan Advent Calendar 2015](http://qiita.com/advent-calendar/2015/sansan)の17日目の記事です。

RxJavaにおいてシンプルにSubscriptionのunsubscribe()を行うためのライブラリ[Subskription](https://github.com/yamamotoj/Subskription)というライブラリを作りましたので、その紹介をしたいと思います。


# 解決したい問題

これまで、RxJavaを用いて非同期処理などを行う場合は、非同期処理が完了する前に利用する側のクラスが破棄される場合など、適切にその処理をunsubscribe()する必要がありました。
そのために`CompositeSubscription`がよく使用されます。


例えばAndroidのActivityでは通信中に画面遷移が起こり、画面上のObjectが破棄されることがあるため、`onPause()`などでunsubscribe()する必要があります。このボイラープレート的に必要となる`CompositeSubscription`の定義がRxJavaをAndroid等で扱う場合に厄介になります。

```kotlin:CompositeSubscriptionを使ったunsubscribe()
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で以下のように指定してください。

```groovy:build.gradle
dependencies {
    compile 'com.github.yamamotoj:subskription:0.2.1'
}

repositories {
    maven { url "https://dl.bintray.com/yamamotoj/maven" }
}

```

# 使い方

## Overview

まず、RxJavaを使用する`SomeActiviry`クラスでは

```kotlin
AutoUnsubscribable by AutoUnsubscribableDelegate()
```

のように記述し、`AutoUnsubscribable`インターフェイスを`AutoUnsubscribableDelegate`のclass delegateを使って実装するようにします。

そうすることで、`AutoUnsubscribableDelegate`クラスの内部でCompositeSubscriptionが保持されるため、`SomeActivity`クラスで`CompositeSubscription`を定義する必要はありません。

そして、`Observable<T>`の拡張関数である`autoUnsubscribe(key:Any)`をsubscribe時に呼んでおくことで、同じ`key`に合致する`unsubscribe(key:Any)`の呼び出しタイミングで登録されたsubscriptionが`unsubscribe()`されます。


```kotlin:Subskriptionを使った例

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`クラスの拡張関数としても定義されているので、以下のような実装が可能です。

```kotlin:Subscriptionの拡張関数として
    fun onClick() {
        loadData()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    // Do something
                }
                .autoUnsubscribe("onPause") // subscribe()後にもくっつけられる

    }
```


## AutoUnsubscribableの階層構造

AutoUnsubscribableインターフェイスは子要素を持つことができます。AutoUnsubscribableが子要素をもったとき、に親のAutoUnsubscribableの`unsubscribe()`を呼ぶと、子要素の`unsubscribe()`も呼び出されます。


```kotlin:AutoUnsubscribableの子要素を持たせる
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つがあることに気づいた方もいらっしゃるかと思います。

```kotlin:autoUnsubscribe/unsubscribeの定義

/** 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](http://www.adventar.org/calendars/857)に書いた、[KotlinでAndroidのRxJavaをちょっと便利に](http://qiita.com/boohbah/items/c18b74b7bfe72c1e3aa9)を参照してください。

ちなみに始めてAndroidのライブラリを公開してmavenから取得できるようにしたのですが、けっこう大変でした。