Android
Kotlin

kotlin コールバックを簡単にシーケンシャルにする

More than 1 year has passed since last update.

kotlinを使った開発で、コールバックをシーケンシャルで動かしたい!
となったとき、どんな実装をされていますか?
こんな方法を思いつきましたという一例です

やりたいこと

シーケンシャルに動作させたいメソッドがあるとします
非同期で取得してきたデータの処理は一般的にcallbackで処理を戻してくると思います。

SequentialManager.kt
firstMethod( complete = {
    result -> ...
    secondMethod( complete = {
        result -> ...         
        thirdMethod( complete = {
            result -> ...         
        }}
    }}
})

これだとnestが深くなりますよね。可読性が落ちるので私は好きではありません。もっと可読性を高めたいですよね。

とは言っても以下のような書き方をしても…だめな例です。

SequentialManager.kt
// 最初のメソッド
firstMethod( complete = {  /// --- ①
    result -> ...          /// --- ②
})

// 次のメソッド
secondMethod( complete = { /// --- ③
    result -> ...          /// --- ④
}}

firstMethod(complete:(result:Boolean) -> Unit) {
    Thread {
        ...
        complete.invoke(true)
    }
}

secondMethod(complete:(result:Boolean) -> Unit) {
    Thread {
        ...
        complete.invoke(true)
    }
}

動作としては ①→③→②,④ となってしまい
本当に動作させたい①→②→③→④
にはならないと思います。

そんなときは以下の方法が有効かもしれません

シーケンシャルメソッドのためのコールバック手法

CallBack 用の専用クラス実装

まず、CallBack専用のクラスを実装します
これは、シーケンシャルに実行するために最も重要な処理です

AppSequentialThreadHandler.kt
class AppSequentialThreadHandler(function: KFunction1<@ParameterName(name = "complete") (result: Any?) -> Unit, Unit>) {
    private var mfunction = function
    private var mResult:Any? = null

    init {
        // スレッドを停止するためのCountDownLatch生成
        val countdownlatch = java.util.concurrent.CountDownLatch(1)
        // Threadの生成
        Thread {
            (mfunction){
                // Callback
                result -> mResult = result
                countdownlatch.countDown()
            }
        }.start()
        // 元スレッドは一時停止
        countdownlatch.await()
        //countdownlatch.await(1000,TimeUnit.MILLISECONDS)
    }
    // データを戻したい場合呼び出す
    fun resultCallback():Any? {
        return mResult
    }
}

これは、コールバックを返却するメソッドを抽象化した状態で定義をしています。これにCountDouwLatchを組み合わせます。
CountDownLatchはThreadを生成したところで停止し、生成されたスレッドは抽象化したメソッドを実行します。
コールバックで結果が帰ってくると、元スレッドの停止状態を解除する仕組みです。

これを上で記載の処理と組み合わせると
// 2017/07/08 誤記修正

SequentialManager.kt
// シーケンスを走らせるメソッド
fun seq() { 
    // 最初に走る処理
    AppSequentialThreadHandler(AppProcess::firstMethod)
    // 次に走る処理 上の処理が完了するまで実行されない 
    // データがほしいときはresultCallbackメソッドを実行
    var data = AppSequentialThreadHandler(AppProcess::secondMethod).resultCallback()
}
AppProcess.kt
// 最初の処理の実態
firstMethod(complete:(result:Boolean) -> Unit) {
    Thread {
        ...
        complete(true)
    }
}

// 次の処理の実態
secondMethod(complete:(result:Boolean) -> Unit) {
    Thread {
        ...
        complete(true)
    }
}

メインシーケンスの書き方はシーケンシャルに書くことができるようになります。
UMLでシーケンス図を書いていて、そのままの実装をしたいときにべんりですね!