Combine: что это такое, ключевые понятия и Publisher с Subscriber

Автор: IT Sectr Опубликовано: 2026-03-17 Время чтения: 8 мин

Combine — это фреймворк реактивного программирования от Apple, представленный в iOS 13. По данным Apple Documentation, 2025, Combine предоставляет единый декларативный API для обработки асинхронных событий. Publisher определяет источник данных, а Subscriber подписывается на их получение.

Главное

  • Combine — встроенный фреймворк Apple для реактивного программирования, доступный с iOS 13
  • Publisher — протокол, определяющий источник значений Output с возможностью ошибки Failure
  • Subscriber — протокол, принимающий значения от Publisher с управляемым demand
  • Subject — Publisher, допускающий ручную вставку событий (PassthroughSubject, CurrentValueSubject)
  • Operators — map, filter, combineLatest, flatMap для трансформации и комбинирования потоков

Что такое Combine?

Combine — это декларативный Swift-фреймворк для обработки асинхронных событий во времени. В отличие от callback-подходов, Combine позволяет описывать пайплайны обработки данных через цепочку операторов. Фреймворк интегрирован с Foundation (URLSession, Timer, NotificationCenter) и SwiftUI (ObservableObject, @Published).

Протокол Publisher

Любой тип, реализующий протокол Publisher, обязан определить Output (тип значений) и Failure (тип ошибки). Publisher не активен до подписки — он начинает эмитить события только после вызова subscribe. Это холодная семантика, характерная для реактивных стримов и повышающая эффективность.

Протокол Subscriber

Subscriber получает события через три метода: receive(subscription:) — подтверждение подписки с управлением demand; receive(_:) — получение нового значения; receive(completion:) — завершение потока с успехом или ошибкой. Demand задаёт количество значений, которые Subscriber готов принять — это механизм backpressure.

Встроенные Publisher

Apple предоставляет множество встроенных Publisher: Just эмитит одно значение и завершается, Future — асинхронный результат с замыканием, Deferred — отложенное создание Publisher до момента подписки. URLSession.dataTaskPublisher преобразует сетевой запрос в реактивный поток. Timer.publish создаёт периодический таймер. NotificationCenter.default.publisher превращает уведомления в Publisher. Эти интеграции позволяют подписываться на события без ручного моста.

Publisher и Subscriber в Combine

В Combine Publisher и Subscriber связаны протоколом Subscription. Subscription — это связующий объект, который контролирует поток данных. Разработчику не нужно создавать свои типы Publisher — Apple предоставляет встроенные: Just, Future, Deferred, Fail, Empty, а также Publisher для URLSession и Timer.

Subject в Combine

Subject — это Publisher, в который можно вручную отправлять значения. PassthroughSubject не хранит состояние: подписчик получает только события, которые произошли после подписки. CurrentValueSubject, аналогичный BehaviorRelay в RxSwift, хранит текущее значение и немедленно передаёт его новому подписчику.

Backpressure

Механизм backpressure в Combine регулирует скорость передачи данных. Subscriber сообщает Publisher через subscription.request(.unlimited) или subscription.request(.max(N)), сколько значений он готов обработать. Это предотвращает переполнение буфера при неравномерной скорости producer и consumer.

Операторы Combine

Combine включает более 100 операторов, разбитых на категории: трансформация (map, tryMap, flatMap), фильтрация (filter, compactMap, removeDuplicates), комбинирование (combineLatest, merge, zip), управление временем (debounce, throttle, delay) и обработка ошибок (catch, replaceError, retry).

Операторы combineLatest объединяет последние значения двух Publisher — каждый раз, когда любой из них эмитит новое значение, замыкание вызывается с обоими обновлёнными значениями. Этот оператор незаменим при валидации форм, где нужно отслеживать состояние нескольких полей ввода одновременно.

Оператор debounce задерживает публикацию значений, пока не пройдёт указанный интервал без новых событий. Это критически важно для поисковых строк: запрос на сервер уходит только после того, как пользователь перестал печатать на 300-500 мс, что снижает нагрузку в 5-10 раз.

Обработка ошибок

Combine предлагает несколько стратегий обработки ошибок на уровне операторов. catch перехватывает Failure и заменяет Publisher на запасной — поток продолжается без краша. replaceError(with:) подставляет значение по умолчанию вместо ошибки. retry(_:) повторяет подписку заданное количество раз при сбое. Важно: ошибка типа Never в сигнатуре Publisher гарантирует, что поток никогда не завершится ошибкой — это позволяет использовать Publisher в SwiftUI без обработки Failure.

Примеры кода Combine

Первый пример показывает базовую работу с Just — Publisher, который эмитит одно значение и завершается:

swift
let publisher = Just("Hello Combine")

publisher
    .sink(receiveCompletion: { completion in
        print("Завершено: \(completion)")
    }, receiveValue: { value in
        print("Получено: \(value)")
    })

Второй пример демонстрирует интеграцию Combine с URLSession для сетевого запроса с обработкой результата на главном потоке:

swift
let url = URL(string: "https://api.example.com")!

URLSession.shared
    .dataTaskPublisher(for: url)
    .map { $0.data }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Ошибка: \(error)")
        }
    }, receiveValue: { data in
        print("Данных получено: \(data.count)")
    })

Третий пример — использование CurrentValueSubject для управления состоянием:

swift
let counter = CurrentValueSubject<Int, Never>(0)

counter
    .sink { value in
        print("Счётчик: \(value)")
    }

counter.send(1) // Выведет: Счётчик: 1
counter.send(2) // Выведет: Счётчик: 2
print(counter.value) // Выведет: 2

Combine vs RxSwift

Combine и RxSwift решают одну задачу — реактивное программирование, но с разными подходами. Combine — нативный фреймворк Apple, встроенный в систему: не требует установки зависимостей, интегрирован с SwiftUI и Foundation. RxSwift — сторонняя библиотека с поддержкой iOS 9+ и более широким набором операторов.

Производительность Combine выше в типовых сценариях благодаря нативной реализации на C. RxSwift предоставляет больше инструментов для отладки (RxSwift.Resources.total, Debug), но его объектно-ориентированная архитектура может быть менее эффективной на больших потоках данных. Выбор между ними — компромисс между возможностями и интеграцией.

Важное различие — модель backpressure. В Combine Subscriber управляет demand через Subscription, что даёт явный контроль над скоростью потока. В RxSwift backpressure по умолчанию не контролируется — Observable эмитит все значения, и Subscriber обрабатывает их по мере поступления. Для крупных потоков данных Combine оказывается предсказуемее и безопаснее по памяти.

Combine глубоко интегрирован с SwiftUI на уровне языка. @Published генерирует Publisher, ObservableObject предоставляет objectWillChange Publisher. SwiftUI View автоматически подписывается на эти Publisher и перерисовывается при изменениях. Для UIKit есть сторонние обёртки CombineCocoa, но Combine изначально проектировался для SwiftUI, поэтому в UIKit его интеграция требует дополнительного кода. RxSwift, напротив, имеет богатую экосистему для UIKit: RxCocoa предоставляет реактивные обёртки для всех UI-элементов.

Combine поддерживает интеграцию с Core Data через NSFetchedResultsController Publisher. Publisher эмитит новые данные при каждом изменении Core Data стека: добавление, удаление, обновление записей. Это позволяет строить реактивные списки без ручного вызова reloadData. Комбинируя Combine с NotificationCenter Publisher для UIApplication.willResignActiveNotification, разработчик создаёт реактивный слой для всего жизненного цикла приложения без делегатов и колбэков. Для ObservableObject классов SwiftUI автоматически обновляет View при изменении @Published свойств — Combine обеспечивает всю связку без единой строки ручного кода. Модульное тестирование Combine-кода выполняется через XCTestExpectation и Publisher.sink в тестовых кейсах с изоляцией Scheduler через ImmediateScheduler.

При работе с Combine в UIKit проектах разработчики часто используют подписку через sink и хранят AnyCancellable в Set. Типичный паттерн: ViewController создаёт вьюху, хранит cancellables, подписывается на Publisher из ViewModel. При deinit все подписки автоматически отменяются через Cancellables-коллекцию. Для правильной работы с UIControl в Combine можно использовать UIControl.Event Publisher через publisher(for:) расширение.

Отладка Combine осуществляется через оператор print() или handleEvents(). print(String) логирует все события: receive subscription, request demand, receive value, receive completion. handleEvents даёт более тонкий контроль: можно указать замыкания для каждого этапа жизненного цикла Publisher. Для тестирования Combine существует CombineExpectations — библиотека, которая позволяет проверять, что Publisher эмитит ожидаемые значения в тестах с виртуальным временем и контролем demand.

Combine на практике

Combine активно используется в SwiftUI для управления формами: каждый @Published property генерирует Publisher, SwiftUI подписывается на него через View.body. Комбинируя несколько Publisher через combineLatest, валидация формы становится декларативной: каждый Publisher отслеживает одно поле, combineLatest собирает все значения, map проверяет их и возвращает статус кнопки Submit. Publishers.Merge объединяет несколько Publisher одного типа в один — полезно для сбора событий с разных контролов в единый поток.

Для интеграции Combine с UIKit используйте UIControl Publisher через расширение: button.publisher(for: .touchUpInside) возвращает Publisher, который эмитит событие при нажатии. CombineCocoa — библиотека от сообщества, предоставляющая Publisher для всех UIControl событий, UITextView.text, UIScrollView.contentOffset. При отписке cancellables Store очищается в deinit, гарантируя, что ни одна подписка не переживёт своего владельца. Для UIControl с множественными состояниями используйте Publishers.MergeMany для объединения Publisher в массив.

Часто задаваемые вопросы

С какой версии iOS доступен Combine?

Combine доступен с iOS 13, macOS 10.15, tvOS 13 и watchOS 6. Это ограничивает его использование в проектах, требующих поддержки более старых версий. Для iOS 12 и ниже применяется RxSwift или сторонние библиотеки.

Чем отличается PassthroughSubject от CurrentValueSubject?

PassthroughSubject не хранит состояние и не повторяет последнее значение новым подписчикам — они получают только будущие события. CurrentValueSubject хранит текущее значение и немедленно передаёт его новому подписчику. CurrentValueSubject.value позволяет читать и писать текущее значение синхронно.

Как Combine интегрируется со SwiftUI?

Combine лежит в основе SwiftUI: @Published генерирует Publisher, ObservableObject использует objectWillChange Publisher. SwiftUI View автоматически подписывается на @ObservedObject и @StateObject через Combine-пайплайн, перерисовывая View при изменении свойств.

Что такое backpressure в Combine?

Backpressure — механизм управления скоростью передачи данных. Subscriber сообщает Subscription, сколько значений готов обработать (demand). Publisher не может эмитить больше, чем запрошено. Это критично при работе с большими потоками данных из сетевых сокетов.

Как обрабатывать ошибки в Combine?

Оператор catch перехватывает ошибку и заменяет Publisher на fallback. replaceError заменяет ошибку на значение по умолчанию. retry повторяет подписку указанное число раз при ошибке. Все операторы следует размещать до subscribe, чтобы исключить краш при Failure.

Итоги

  • Combine — встроенный фреймворк Apple для реактивного программирования с iOS 13
  • Publisher — протокол с Output и Failure, начинает работу после подписки
  • Subscriber — принимает значения через receive с поддержкой demand-управления
  • Subject (PassthroughSubject, CurrentValueSubject) — Publisher с ручной вставкой событий
  • Операторы map, filter, combineLatest, debounce, catch — основа пайплайна
  • Backpressure через demand предотвращает переполнение буфера Subscriber
  • Выбирайте Combine для новых проектов от iOS 13, RxSwift — для поддержки старых версий

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также