Combine — це фреймворк реактивного програмування від Apple, представлений в iOS 13. За даними Apple Documentation, 2025, Combine надає єдиний декларативний API для обробки асинхронних подій. Publisher визначає джерело даних, а Subscriber підписується на їх отримання.
Головне
Combine — це декларативний Swift-фреймворк для обробки асинхронних подій у часі. На відміну від callback-підходів, Combine дозволяє описувати пайплайни обробки даних через ланцюжок операторів. Фреймворк інтегрований з Foundation (URLSession, Timer, NotificationCenter) і SwiftUI (ObservableObject, @Published).
Будь-який тип, що реалізує протокол Publisher, зобов'язаний визначити Output (тип значень) і Failure (тип помилки). Publisher не активний до підписки — він починає емітити події лише після виклику subscribe. Це холодна семантика, характерна для реактивних стримів і підвищує ефективність.
Subscriber отримує події через три методи: receive(subscription:) — підтвердження підписки з керуванням demand; receive(_:) — отримання нового значення; receive(completion:) — завершення потоку з успіхом або помилкою. Demand задає кількість значень, які Subscriber готовий прийняти — це механізм backpressure.
Apple надає безліч вбудованих Publisher: Just емітить одне значення і завершується, Future — асинхронний результат із замиканням, Deferred — відкладене створення Publisher до моменту підписки. URLSession.dataTaskPublisher перетворює мережевий запит на реактивний потік. Timer.publish створює періодичний таймер. NotificationCenter.default.publisher перетворює сповіщення на Publisher. Ці інтеграції дозволяють підписуватися на події без ручного мосту.
У Combine Publisher і Subscriber пов'язані протоколом Subscription. Subscription — це сполучний об'єкт, який контролює потік даних. Розробнику не потрібно створювати власні типи Publisher — Apple надає вбудовані: Just, Future, Deferred, Fail, Empty, а також Publisher для URLSession і Timer.
Subject — це Publisher, у який можна вручну надсилати значення. PassthroughSubject не зберігає стан: підписник отримує лише події, які сталися після підписки. CurrentValueSubject, аналогічний BehaviorRelay у RxSwift, зберігає поточне значення і негайно передає його новому підписнику.
Механізм backpressure у Combine регулює швидкість передачі даних. Subscriber повідомляє Publisher через subscription.request(.unlimited) або subscription.request(.max(N)), скільки значень він готовий обробити. Це запобігає переповненню буфера при нерівномірній швидкості producer і consumer.
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.
Перший приклад показує базову роботу з Just — Publisher, який емітить одне значення і завершується:
let publisher = Just("Hello Combine")
publisher
.sink(receiveCompletion: { completion in
print("Завершено: \(completion)")
}, receiveValue: { value in
print("Отримано: \(value)")
})
Другий приклад демонструє інтеграцію Combine з URLSession для мережевого запиту з обробкою результату на головному потоці:
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 для керування станом:
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 і 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 активно використовується в SwiftUI для керування формами: кожна @Published властивість генерує 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 у масив.
Часті запитання
Combine доступний з iOS 13, macOS 10.15, tvOS 13 і watchOS 6. Це обмежує його використання в проєктах, що вимагають підтримки старіших версій. Для iOS 12 і нижче застосовується RxSwift або сторонні бібліотеки.
PassthroughSubject не зберігає стан і не повторює останнє значення новим підписникам — вони отримують лише майбутні події. CurrentValueSubject зберігає поточне значення і негайно передає його новому підписнику. CurrentValueSubject.value дозволяє читати і записувати поточне значення синхронно.
Combine лежить в основі SwiftUI: @Published генерує Publisher, ObservableObject використовує objectWillChange Publisher. SwiftUI View автоматично підписується на @ObservedObject і @StateObject через Combine-пайплайн, перемальовуючи View при зміні властивостей.
Backpressure — механізм керування швидкістю передачі даних. Subscriber повідомляє Subscription, скільки значень готовий обробити (demand). Publisher не може емітити більше, ніж запитано. Це критично при роботі з великими потоками даних із мережевих сокетів.
Оператор catch перехоплює помилку і замінює Publisher на fallback. replaceError замінює помилку на значення за замовчуванням. retry повторює підписку вказану кількість разів при помилці. Усі оператори слід розміщувати до subscribe, щоб виключити краш при Failure.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також