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

Какие знаешь Subject в RxSwift?

2.0 Middle🔥 141 комментариев
#Многопоточность и асинхронность#Язык Swift

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

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

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

Subject в RxSwift: связующее звено между мирами Reactive и Imperative

Subject в RxSwift — это особая разновидность Observable, которая может выполнять и роль наблюдаемой последовательности, и роль наблюдателя. Это делает их ключевым инструментом для преобразования императивного кода (например, UI-событий или колбэков) в реактивные потоки данных. Они являются "мостом" или "прокси", позволяющим "вручную" отправлять события (next, error, completed) в последовательность, на которую уже могут быть подписаны другие слушатели.

В RxSwift существует четыре основных типа Subject, каждый с уникальной семантикой поведения относительно подписчиков.

Основные типы Subject

1. PublishSubject

Наиболее простой и распространенный Subject. Он начинает жизнь "пустым" и рассылает своим подписчикам только те элементы, которые были испущены после момента подписки. Если до подписки Subject уже завершился с ошибкой или completed, новый подписчик немедленно получит соответствующее терминальное событие.

let publishSubject = PublishSubject<String>()

// Эмитируем элемент ДО подписки - его никто не получит
publishSubject.onNext("Привет, мир 1")

let subscription1 = publishSubject.subscribe(onNext: { value in
    print("Подписчик 1: \(value)")
})

// Эти элементы получат все текущие подписчики
publishSubject.onNext("Привет, мир 2") // Напечатает: Подписчик 1: Привет, мир 2
publishSubject.onNext("Привет, мир 3") // Напечатает: Подписчик 1: Привет, мир 3

let subscription2 = publishSubject.subscribe(onNext: { value in
    print("Подписчик 2: \(value)")
})

publishSubject.onNext("Привет, мир 4")
// Напечатает:
// Подписчик 1: Привет, мир 4
// Подписчик 2: Привет, мир 4

Типичное использование: моделирование событий, где не требуется история (например, нажатия кнопок, UITextField события редактирования, уведомления о действиях пользователя).

2. BehaviorSubject

Похож на PublishSubject, но обязательно имеет начальное (или последнее испущенное) значение. Каждый новый подписчик мгновенно получает этот последний элемент (или начальное значение) при подписке, а затем все последующие.

// Обязательно указываем initial value
let behaviorSubject = BehaviorSubject<String>(value: "Начальное значение")

let subscription1 = behaviorSubject.subscribe(onNext: { value in
    print("Подписчик 1: \(value)")
})
// Мгновенно напечатает: Подписчик 1: Начальное значение

behaviorSubject.onNext("Обновление 1") // Напечатает: Подписчик 1: Обновление 1

let subscription2 = behaviorSubject.subscribe(onNext: { value in
    print("Подписчик 2: \(value)")
})
// Новый подписчик мгновенно получит последнее значение:
// Напечатает: Подписчик 2: Обновление 1

Типичное использование: хранение и передача "текущего состояния" (например, текущий выбранный элемент в UITableView, значение UISwitch, данные для отображения, которые должны быть актуальны для новых подписчиков).

3. ReplaySubject

Буферизует определенное количество последних испущенных элементов (или все элементы, если используется ReplaySubject.createUnbounded()) и выдает этот буфер каждому новому подписчику сразу после подписки.

// Буферизуем 2 последних элемента
let replaySubject = ReplaySubject<String>.create(bufferSize: 2)

replaySubject.onNext("Элемент 1")
replaySubject.onNext("Элемент 2")
replaySubject.onNext("Элемент 3")

let subscription = replaySubject.subscribe(onNext: { value in
    print("Подписчик: \(value)")
})
// Новый подписчик получит буфер из 2-х последних элементов:
// Напечатает:
// Подписчик: Элемент 2
// Подписчик: Элемент 3

Типичное использование: ситуации, где новым подписчикам критически важна некоторая история событий (например, кэшированные данные, последние N сообщений в чате, которые нужно показать при подключении).

4. AsyncSubject

Самый необычный Subject. Он испускает только последний элемент последовательности (или completed без элемента, если последовательность пуста) и только после того, как последовательность завершится (completed). Если последовательность завершилась с ошибкой (error), AsyncSubject не испускает никаких элементов, а только передает эту ошибку.

let asyncSubject = AsyncSubject<String>()

asyncSubject.subscribe(onNext: { value in
    print("Получено: \(value)")
}, onCompleted: {
    print("Завершено")
})

asyncSubject.onNext("Элемент 1") // Ничего не печатает
asyncSubject.onNext("Элемент 2") // Ничего не печатает
asyncSubject.onNext("Последний элемент") // Ничего не печатает
asyncSubject.onCompleted()
// Только после onCompleted подписчик получит результат:
// Напечатает: Получено: Последний элемент
// Напечатает: Завершено

Типичное использование: сценарии, где важен исключительно итоговый результат операции (например, результат тяжелого вычисления, финальный статус длительной задачи).

Ключевые различия и выбор Subject

  • История для новых подписчиков: PublishSubject — нет истории; BehaviorSubject — одно последнее значение; ReplaySubject — буфер заданного размера; AsyncSubject — только финальный результат после завершения.
  • Время эмиссии: AsyncSubject эмитит значение только после завершения, остальные — немедленно при вызове onNext.
  • Выбор зависит от требований: Нужно ли новому UI-компоненту текущее состояние? Используй BehaviorSubject. Важны последние N событий? ReplaySubject. Просто событие? PublishSubject.

Важные производные типы

На практике также часто используются обертки над Subject для специфичных нужд:

  • PublishRelay и BehaviorRelay (из RxCocoa): Являются обертками над PublishSubject и BehaviorSubject соответственно, но не могут принимать терминальные события (error или completed). Это делает их идеальными для моделирования бесконечных потоков, таких как UI-события, где "завершение" потока обычно не имеет смысла. BehaviorRelay особенно популярен для хранения состояния в реактивном стиле, так как предоставляет удобное свойство .value для чтения текущего значения.
import RxCocoa

let behaviorRelay = BehaviorRelay<String>(value: "Начало")
print(behaviorRelay.value) // Чтение текущего значения: "Начало"
behaviorRelay.accept("Новое значение") // Эмиссия нового значения (вместо onNext)

Понимание семантики каждого Subject — основа для написания корректного, предсказуемого и эффективного реактивного кода на RxSwift.