1

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

我尝试使用带有以下代码的协程,但在 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 库,但我觉得这应该是可能的,而不需要依赖,我想学习如何。

4

2 回答 2

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

于 2021-04-15T20:43:34.013 回答
0

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

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

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

于 2021-05-19T13:45:44.210 回答