> Какие виды скоупов существуют в Kotlin Coroutines (Kotlin, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Radium Finance

Стек: Kotlin, Android

> Пример ответа

Короткий ответ

В Kotlin Coroutines существует три основных вида скоупов: CoroutineScope (общий интерфейс), GlobalScope (глобальный, живёт всё время приложения) и CoroutineScope с Job/SupervisorJob (структурированный, привязанный к жизненному циклу). На практике в Android используются viewModelScope, lifecycleScope и кастомные скоупы с SupervisorJob. Ключевое различие - время жизни и поведение при отмене родительской корутины.

Подробное объяснение

CoroutineScope - это интерфейс, который содержит CoroutineContext. Он определяет область, в которой запускаются корутины. Основные реализации:

  1. GlobalScope - синглтон, не привязан к жизненному циклу. Корутины в нём живут, пока работает приложение. Использовать не рекомендуется, так как легко получить утечку памяти.

  2. Структурированный скоуп - создаётся через CoroutineScope(Dispatchers.Main + Job()). Жизненный цикл управляется явно через cancel() или через родительский Job. Все дочерние корутины отменяются вместе с родительским скоупом.

  3. Специализированные скоупы в Android:

    • viewModelScope - привязан к ViewModel, отменяется при onCleared()
    • lifecycleScope - привязан к LifecycleOwner, отменяется при onDestroy()
    • repeatOnLifecycle - дополнительный механизм для реактивных потоков
  4. SupervisorJob - вариант Job, где отмена одной дочерней корутины не отменяет остальные. Используется для параллельных независимых задач.

Важный момент: скоуп - это не поток, а контекст. Он определяет Dispatcher, Job и другие элементы контекста.

На практике

В Android-разработке:

  • Для UI-событий используем lifecycleScope с Dispatchers.Main
  • Для бизнес-логики - viewModelScope
  • Для фоновых задач - Dispatchers.IO внутри скоупа
  • Для независимых параллельных задач - SupervisorJob в кастомном скоупе
  • Для длительных операций, переживающих смену конфигурации, - скоуп, привязанный к Application

Правило: скоуп должен быть отменён, когда его владелец уничтожается. Никогда не используем GlobalScope без веской причины.

Пример кода

// viewModelScope - стандартный для Android
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _uiState.value = result
        }
    }
}

// Кастомный скоуп с SupervisorJob
class MyRepository {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
    fun fetchData(): Deferred<String> = scope.async {
        delay(1000)
        "data"
    }
    
    fun cleanup() {
        scope.cancel()
    }
}

// lifecycleScope в Activity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    render(state)
                }
            }
        }
    }
}

Как отвечать на собеседовании

Начни с определения скоупа как контейнера контекста. Затем перечисли основные виды: GlobalScope, структурированный скоуп, специализированные Android-скоупы. Обязательно упомяни разницу между Job и SupervisorJob. Приведи пример из реальной практики - viewModelScope для загрузки данных. Подчеркни важность структурированной конкурентности и отмены корутин. Если спросят про Dispatchers - свяжи их со скоупами, но не уходи в детали, если не просят.

Что проверяет интервьюер

  • Понимание структурированной конкурентности
  • Знание жизненного цикла корутин в Android
  • Умение объяснить разницу между Job и SupervisorJob
  • Осознание проблем GlobalScope
  • Практический опыт с viewModelScope и lifecycleScope
  • Понимание, как скоуп связан с CoroutineContext

Типичные ошибки

  • Путают CoroutineScope и CoroutineContext - это разные вещи
  • Считают, что GlobalScope - нормальное решение для фоновых задач
  • Не знают, что SupervisorJob не отменяет соседние корутины
  • Забывают про repeatOnLifecycle для безопасного сбора потоков
  • Используют viewModelScope для задач, которые должны пережить ViewModel
  • Не вызывают cancel() в кастомных скоупах
  • Путают launch и async в контексте скоупа

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью