← Назад к вопросам

Что такое sharedIn?

1.7 Middle🔥 111 комментариев
#Kotlin основы#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SharedIn: анонимный объект в Coroutines

SharedIn — это не самостоятельная функция или тип, а часть расширяющего (extension) метода .shareIn(...) в библиотеке Kotlin Coroutines. Он применяется к объектам Flow<T> и предназначен для преобразования "холодного" (cold) потока данных в "горячий" (hot), который может быть разделен между несколькими подписчиками.

Ключевая концепция: от Cold к Hot Flow

  • Cold Flow: Стандартный Flow. Каждая новая подписка (collect) запускает заново весь код построения потока (в блоке flow { ... }). Данные производятся независимо для каждого коллектора. Это похоже на чтение файла — каждый читатель открывает свой экземпляр.
  • Hot Flow (SharedFlow): Данные производятся независимо от наличия подписчиков. Все подписчики получают одни и те же данные, эмитированные после их подписки. Это похоже на трансляцию радио — вещание идет постоянно, а слушатели подключаются к уже идущему потоку.

Метод .shareIn() именно это и делает: он берет исходный холодный поток и запускает его в рамках корутины, жизненный цикл которой управляется CoroutineScope. Полученный экземпляр SharedFlow затем может быть использован многими потребителями.

Сигнатура и параметры метода

fun <T> Flow<T>.shareIn(
    scope: CoroutineScope,
    started: SharingStarted,
    replay: Int = 0
): SharedFlow<T>

Рассмотрим параметры подробно:

  1. scope: CoroutineScope (обязательный):
    *   Определяет жизненный цикл общего потока. Когда этот scope отменяется (`cancel`), останавливается и совместно используемый поток. Обычно используется `viewModelScope` в Android (чтобы переживать изменения конфигурации) или `applicationScope`.

  1. started: SharingStarted (обязательный):
    *   Стратегия запуска общей подписки на исходный поток. Имеет три основные предопределенные стратегии:
    *   **`SharingStarted.Eagerly`**: Подписка на исходный поток начинается сразу при создании `SharedFlow`, даже если нет активных коллекторов. Данные могут быть потеряны, если никто не успел подписаться.
    *   **`SharingStarted.Lazily`**: Подписка начинается только при появлении первого подписчика и активна до отмены `scope`. Последующие подписчики получают `replay`-буфер.
    *   **`SharingStarted.WhileSubscribed(stopTimeoutMillis = 0, replayExpirationMillis = Long.MAX_VALUE)`**: **Наиболее полезная стратегия для Android.** Подписка активируется при появлении первого подписчика и останавливается после исчезновения последнего (с задержкой `stopTimeoutMillis`). Это позволяет экономить ресурсы. `replayExpirationMillis` контролирует, как долго хранится `replay`-кэш после остановки.

  1. replay: Int = 0 (опциональный):
    *   Определяет размер буфера повторно разосланных данных. Каждому новому подписчику сразу будут отправлены последние `replay` значений. Например, `replay = 1` гарантирует, что новый коллектор получит самое актуальное состояние.

Практический пример использования в Android (MVVM)

Допустим, у нас есть репозиторий, который предоставляет поток обновлений из сети или БД, и мы хотим делиться им между несколькими UI-компонентами во ViewModel.

class MyViewModel(private val dataRepository: DataRepository) : ViewModel() {
    // Репозиторий возвращает холодный Flow
    private val coldFlowFromRepo: Flow<Data> = dataRepository.getDataStream()

    // Преобразуем его в SharedFlow, который живет в рамках viewModelScope
    val sharedData: SharedFlow<Data> = coldFlowFromRepo
        .shareIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000), // Останавливаемся через 5 сек после потери подписчиков
            replay = 1 // Новый подписчик сразу получит последний актуальный Data объект
        )

    // Теперь несколько Fragments/Compose функций могут коллектить `sharedData`,
    // и при этом исходный запрос к репозиторию выполнится только один раз.
}

Ключевые отличия от .stateIn()

Часто упоминается вместе с .stateIn(...), который создает StateFlow. Главные различия:

  • StateFlow — это специализированный SharedFlow с replay = 1 и обязательным начальным значением. Он всегда хранит и немедленно передает текущее состояние.
  • SharedFlow через .shareIn() более гибок: можно не иметь начального значения (replay = 0), иметь буфер больше 1 для обработки событий (например, одноразовых UI-событий типа Snackbar), где потеря события допустима.

Итог

.shareIn() — это мощный инструмент для оптимизации производительности и организации данных в приложении на Kotlin Coroutines. Он предотвращает многократное выполнение дорогостоящих операций (сетевые запросы, чтение БД) за счет создания единого источника истины (SharedFlow) в управляемом CoroutineScope. Для работы с состоянием UI чаще используют его "родственника" — .stateIn(), но для потоков событий или кастомных сценариев .shareIn() является незаменимым выбором.