Реализуйте таймер в общем коде в Kotlin Multiplatform Mobile
Я пытаюсь реализовать функцию таймера в общем коде проекта Kotlin Multiplatform Mobile. Таймер должен работать в течение n секунд, и каждую секунду он должен перезванивать для обновления пользовательского интерфейса. Более того, кнопка в пользовательском интерфейсе может отменить таймер. Это неизбежно означает, что я должен начать какой-то новый поток, и мой вопрос в том, какой механизм лучше использовать - рабочие, сопрограммы или что-то еще?
Я пробовал использовать сопрограмму со следующим кодом, но столкнулся с InvalidMutabilityException на iOS:
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 ответа
Как вы подозреваете в комментарии,
updateInterface
является свойством содержащего класса, поэтому захват ссылки на него в лямбде также заморозит родительский объект. Это, вероятно, самый распространенный и запутанный способ заморозить ваш класс.
Я бы попробовал что-то вроде этого:
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()
}
}
Это немного многословно, но сделайте локальный val для обратного вызова, прежде чем записывать его в лямбда.
Кроме того, добавив
ensureNeverFrozen()
предоставит вам трассировку стека до точки, в которой класс заморожен, а не позже в вызове.
Подробнее см. Https://www.youtube.com/watch?v=oxQ6e1VeH4M&t=1429s и несколько упрощенную серию сообщений в блоге: https://dev.to/touchlab/practical-kotlin-native-concurrency-ac7
Я проделал то же самое в одной из задач, используя функцию расширения для области сопрограммы:
fun CoroutineScope.Ticker(
tickInMillis: Long,
onTick: () -> Unit
) {
this.launch(Dispatchers.Default) {
while (true) {
withContext(Dispatchers.Main) { onTick() }
delay(tickInMillis)
}
}
}
Сначала реализуйте диспетчеров для обеих платформ, а затем вызовите это в подходящей области.