Корутины в KMP и нюансы iOS
Корутины — общий язык асинхронности в KMP; разбираем диспетчеры и подводные камни iOS.
Корутина — приостанавливаемая единица асинхронной работы; в KMP корутины работают на всех платформах и являются стандартным способом писать неблокирующий общий код.
Зачем корутины общему коду
Сетевой запрос, чтение из базы, любая долгая операция в общем коде — это suspend-функция. Корутины дают единый, платформенно-нейтральный способ их выполнять и комбинировать. Библиотека kotlinx.coroutines мультиплатформенна и идёт в commonMain.
Dispatchers по платформам
Диспетчер решает, на каком потоке выполняется корутина. Dispatchers.Default (пул для CPU-задач) и Dispatchers.Main (UI-поток) есть на обеих платформах. А вот Dispatchers.IO исторически был только на JVM; в общем коде для блокирующего ввода-вывода полагаются на Default или платформенную реализацию. На iOS Dispatchers.Main — это главная очередь, корректная для обновления SwiftUI.
// commonMain
class OrderRepository(private val api: OrderApi) {
suspend fun refresh(): List<OrderDto> =
withContext(Dispatchers.Default) { api.fetchOrders() }
}Структурированная конкурентность
Корутины запускаются в CoroutineScope, который владеет их жизненным циклом: отменили scope — отменились все его корутины. В общем ViewModel это критично: при уходе с экрана отменяем scope, и зависшие запросы не утекают.
Как работает под капотом на iOS
Раньше Kotlin/Native имел жёсткую модель памяти: объект, переданный в другой поток, нужно было «заморозить», иначе падение. Это сильно усложняло многопоточные корутины на iOS. С новым менеджером памяти Kotlin/Native (memory manager) это ограничение снято — объекты свободно делятся между потоками, как на JVM. Современный KMP-проект пишет корутины почти одинаково для обеих платформ. Тем не менее на iOS остаётся нюанс: длинные CPU-задачи на главной очереди подвесят UI так же, как на любой платформе, поэтому тяжёлое выносят на Default.
Вызов из Swift
suspend-функции автоматически экспортируются в Swift как функции с completion-обработчиком (а в новых версиях — как async). Подробнее — в разделе про интероп. Главное помнить: из общего кода вы отдаёте Swift корутинный API, а не блокирующий.
Частые ошибки
Полагаться на Dispatchers.IO в общем коде — его там может не быть; используйте Default или абстрагируйте диспетчер. Вторая ошибка — запускать корутины в «глобальном» scope без привязки к экрану: они переживут экран и утекут. Третья — держать в голове старую модель «замораживания»; на современном KMP она неактуальна, не усложняйте код её обходами.
Итоги
- Корутины — общий способ асинхронности в KMP, библиотека мультиплатформенна.
Default/Mainесть везде;IO— нюанс, в общем коде на него не полагаются.- Структурированная конкурентность через scope предотвращает утечки.
- Новый менеджер памяти Kotlin/Native снял ограничения «замораживания» на iOS.