在 Kotlin Multiplatform Mobile 的共享代码中实现计时器

时间:2021-04-15 09:01:34

标签: kotlin-native kotlin-multiplatform-mobile

我正在尝试在 Kotlin Multiplatform Mobile 项目的共享代码中实现计时器功能。计时器应运行 n 秒,并且每秒应回调以更新 UI。此外,UI 中的按钮可以取消计时器。这不可避免地意味着我必须启动某种新线程,我的问题是使用哪种机制是合适的——worker、协同程序或其他什么?

我尝试使用具有以下代码的协程,但在 iOS 上遇到了 InvalidMutabilityException:

class Timer(val updateInterface: (Int) -> Unit) {
    private var timer: Job? = null

    fun start(seconds: Int) {
        timer = CoroutineScope(EmptyCoroutineContext).launch {
            repeat(seconds) {
                updateInterface(it)
                delay(1000)
            }
            updateInterface(seconds)
        }
    }

    fun stop() {
        timer?.cancel()
    }
}

我确实了解 moko-time 库,但我觉得这应该可以在不承担依赖的情况下实现,我想了解如何操作。

2 个答案:

答案 0 :(得分:2)

正如您在评论中怀疑的那样,updateInterface 是包含类的一个属性,因此在 lambda 中捕获对该属性的引用也会冻结父类。这可能是冻结课程的最常见和最令人困惑的方式。

我会尝试这样的事情:

class Timer(val updateInterface: (Int) -> Unit) {
    private var timer: Job? = null

    init {
        ensureNeverFrozen()
    }

    fun start(seconds: Int) {
        val callback = updateInterface
        timer = CoroutineScope(EmptyCoroutineContext).launch {
            repeat(seconds) {
                callback(it)
                delay(1000)
            }
            callback(seconds)
        }
    }

    fun stop() {
        timer?.cancel()
    }
}

它有点冗长,但在 lambda 中捕获它之前为回调创建一个本地 val。

此外,添加 ensureNeverFrozen() 将为您提供一个堆栈跟踪,直到类被冻结而不是在调用后期。

有关详细信息,请参阅 https://www.youtube.com/watch?v=oxQ6e1VeH4M&t=1429s 和稍微简化的博客文章系列:https://dev.to/touchlab/practical-kotlin-native-concurrency-ac7

答案 1 :(得分:0)

我在其中一项任务中做了类似的事情,使用了协程作用域的扩展函数:

fun CoroutineScope.Ticker(
    tickInMillis: Long,
    onTick: () -> Unit
) {
    this.launch(Dispatchers.Default) {
        while (true) {
            withContext(Dispatchers.Main) { onTick() }
            delay(tickInMillis)
        }
    }
}

首先为两个平台实现调度程序,然后在合适的范围内调用它。