我正在尝试在 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 库,但我觉得这应该可以在不承担依赖的情况下实现,我想了解如何操作。
答案 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)
}
}
}
首先为两个平台实现调度程序,然后在合适的范围内调用它。