RxSwift — to biblioteka programowania reaktywnego dla iOS, implementująca wzorzec Observable i funkcjonalne operatory do pracy z asynchronicznymi strumieniami danych. Portuje koncepcje ReactiveX (Rx) do języka Swift, dostarczając jednolity API do obsługi zdarzeń z UI, zapytań sieciowych, baz danych i timerów. Według danych ReactiveX, 2025, biblioteka jest używana w ponad 60 000 projektów na GitHub i pozostaje standardem programowania reaktywnego dla Swift aż do pojawienia się natywnego Combine. RxSwift zastępuje delegaty, domknięcia i powiadomienia jednolitym łańcuchem przetwarzania zdarzeń.
Najważniejsze
RxSwift — to Swift-port biblioteki ReactiveX (Rx), stworzonej przez firmę Microsoft dla .NET w 2012 roku. Główna idea Rx — przedstawić każdy asynchroniczny źródło danych jako Observable sekwencję, do której można stosować funkcjonalne operatory. W iOS-developmentie RxSwift jest używany do łączenia zdarzeń UI (naciśnięcia przycisków, wprowadzanie tekstu, gesty) z logiką aplikacji bez jawnych delegatów i target-action.
Biblioteka składa się z trzech modułów: RxSwift (rdzeń — Observable, Operator, Scheduler), RxCocoa (integracja z UIKit — rx-rozszerzenia dla UIButton, UITextField, UITableView) i RxRelay (Subject bez zdarzeń terminalnych). Taki podział pozwala używać rdzenia w serwerowym Swift i testach, podłączając UI-obudowę tylko dla aplikacji iOS.
Według ankiety Stack Overflow, 2025, RxSwift wchodzi do top-5 najczęściej używanych bibliotek zewnętrznych dla iOS. Głównym powodem popularności jest jednolita obsługa wszystkich wzorców asynchronicznych: zapytania sieciowe przez URLSession, animacje przez UIViewPropertyAnimator, powiadomienia przez NotificationCenter i delegaty przez DelegateProxy — wszystko to sprowadza się do Observable z przewidywalnym cyklem życia.
Programowanie reaktywne — to paradygmat, w którym program reaguje na zmiany danych, a nie je odpytuje. Zamiast pisać „pobierz dane → przetwórz → zaktualizuj UI“, programista opisuje łańcuch transformacji: „kiedy dane się zmienią, zastosuj filtr, potem map, potem zaktualizuj UI“. RxSwift implementuje ten paradygmat poprzez Observable łańcuchy z leniwym wykonaniem — nic się nie dzieje, dopóki nie pojawi się subskrybent.
Observable — fundamentalny typ RxSwift, reprezentujący sekwencję zdarzeń w czasie. Observable może wysyłać trzy typy zdarzeń: next (nowa wartość), error (błąd, kończący strumień) i completed (pomyślne zakończenie). Istnieją finite Observable (kończące się po wysłaniu wszystkich wartości) i infinite Observable (np. zdarzenia UI — nigdy się nie kończą).
Cykl życia Observable obejmuje trzy etapy: tworzenie (create, just, from), transformacja (operatory map, filter, flatMap) i subskrypcja (subscribe). Bez subskrypcji Observable nie wykonuje żadnych działań — to leniwa sekwencja. Po subskrypcji Observable zaczyna wysyłać zdarzenia do subskrybenta, dopóki się nie zakończy lub subskrybent nie anuluje subskrypcji przez Disposable.
import RxSwift
// Tworzenie Observable z tablicy
let numbers = Observable.from([1, 2, 3, 4, 5])
// Transformacja przez operatory
let squared = numbers
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
// Subskrypcja z obsługą zdarzeń
let disposable = squared
.subscribe(onNext: { print($0) },
onError: { print("Error: \($0)") },
onCompleted: { print("Done") })
Subject — to typ, który jednocześnie jest Observable (można się na niego subskrybować) i Observer (można do niego wysyłać zdarzenia). RxSwift dostarcza cztery typy Subject: PublishSubject (tylko nowe zdarzenia), BehaviorSubject (z wartością początkową), ReplaySubject (buforowanie ostatnich N zdarzeń) i AsyncSubject (tylko ostatnia wartość przed zakończeniem). Subject jest wygodny do integracji kodu imperatywnego (domknięcia, delegaty) z reaktywnym łańcuchem.
Disposable — to token anulowania subskrypcji. Gdy subskrybent nie chce już otrzymywać zdarzeń, wywołuje disposable.dispose(). W praktyce używa się DisposeBag: kolekcji Disposable, która automatycznie anuluje wszystkie subskrypcje przy deinicjalizacji właściciela (np. UIViewController). Zapobiega to wyciekom pamięci, nieuniknionym przy ręcznym zarządzaniu subskrypcjami.
| Typ Subject | Wartość początkowa | Powtarza subskrybentowi | Zastosowanie |
|---|---|---|---|
| PublishSubject | Nie | Tylko nowe zdarzenia | Zdarzenia UI, rzadkie powiadomienia |
| BehaviorSubject | Tak | Ostatnie + nowe | Stan, strumień danych |
| ReplaySubject | Nie | Bufor N + nowe | Cache’owanie historii |
| AsyncSubject | Nie | Tylko ostatnie | Zadania obliczeniowe |
| PublishRelay | Nie | Tylko nowe | Bez error/completed |
Operatory RxSwift — to funkcje, które przyjmują jeden Observable i zwracają inny Observable, transformując strumień danych. Łącząc operatory, programista buduje deklaratywne łańcuchy przetwarzania bez zmiennych pośrednich i stanów wyścigu. Wszystkie operatory są leniwe: łańcuch buduje się podczas opisu, a wykonuje podczas subskrypcji.
Operatory transformacji zmieniają każde zdarzenie w strumieniu. map stosuje funkcję do każdego elementu, flatMap rozwija Observable z każdego elementu w jeden płaski strumień, scan akumuluje wynik pośredni (analog reduce, ale z emisją każdego kroku). buffer grupuje elementy według czasu lub ilości, window dzieli strumień na zagnieżdżone Observable według okien.
// Przykład łańcucha operatorów: wyszukiwanie z debounce
searchTextField.rx.text
.orEmpty
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query -> Observable<[String]> in
return apiService.search(query)
}
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { _, item, cell in
cell.textLabel?.text = item
}
.disposed(by: disposeBag)
Operatory filtracji przepuszczają tylko te zdarzenia, które spełniają warunek. filter odsiewa według predykatu, distinctUntilChanged przepuszcza tylko wartości różne od poprzedniej, take i takeWhile ograniczają liczbę zdarzeń. skip i skipWhile pomijają pierwsze N zdarzeń lub zdarzenia do spełnienia warunku. Do obsługi błędów używa się catchError (przechwycenie i zamiana) i retry (ponowienie sekwencji przy błędzie).
Operatory łączenia łączą kilka Observable w jeden. combineLatest łączy ostatnie wartości z kilku strumieni w krotkę, zip łączy parami elementy o tych samych indeksach, merge łączy kilka strumieni w jeden w kolejności nadejścia zdarzeń. withLatestFrom łączy zdarzenie z głównego strumienia z ostatnią wartością z innego strumienia — wygodne dla zdarzeń UI z bieżącym stanem.
| Kategoria | Operator | Działanie |
|---|---|---|
| Transformacja | map | Przekształca każdą wartość przez funkcję |
| Transformacja | flatMap | Rozwija zagnieżdżone Observable w płaski strumień |
| Filtracja | filter | Przepuszcza wartości według predykatu |
| Filtracja | distinctUntilChanged | Przepuszcza tylko zmienione wartości |
| Łączenie | combineLatest | Łączy ostatnie wartości z 2+ strumieni |
| Łączenie | zip | Łączy parami elementy według indeksu |
| Zarządzanie | debounce | Opóźnia zdarzenia do pauzy w strumieniu |
| Zarządzanie | take | Pobiera pierwsze N zdarzeń i kończy strumień |
Scheduler w RxSwift — to abstrakcja nad wątkiem wykonania (lub kolejką). Scheduler określa, na którym wątku wykonywane jest tworzenie zdarzeń, transformacja i subskrypcja. W przeciwieństwie do GCD, gdzie programista jawnie wskazuje DispatchQueue, RxSwift używa dwóch parametrów: subscribeOn (na którym wątku wykonywany jest Observable) i observeOn (na którym wątku wywoływany jest subskrybent).
MainScheduler wykonuje kod na głównym wątku — obowiązkowy do aktualizacji UIKit (wszystkie zmiany UI muszą odbywać się na main thread). SerialDispatchQueueScheduler opakowuje sekwencyjną kolejkę GCD, ConcurrentDispatchQueueScheduler — równoległą. Do zadań w tle używa się ConcurrentDispatchQueueScheduler z qos: .background lub .utility.
// Typowy wzorzec: praca w tle + UI na main
apiService.fetchData()
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.observeOn(MainScheduler.instance)
.subscribe(onNext: { data in
self.tableView.reloadData()
self.loadingIndicator.stopAnimating()
})
.disposed(by: disposeBag)
Prawidłowe użycie Scheduler zapobiega blokowaniu UI przy zapytaniach sieciowych i ciężkich obliczeniach. subscribeOn wpływa na cały upstream (wszystkie operatory do observeOn wykonują się na wskazanym planiście). observeOn przełącza downstream — wszystkie operatory po observeOn wykonują się na wskazanym planiście. W łańcuchu może być wiele observeOn do przełączania między wątkami na różnych etapach transformacji.
SerialDispatchQueueScheduler gwarantuje sekwencyjne przetwarzanie zdarzeń, co jest ważne dla bezpieczeństwa wątkowego współdzielonych zasobów. Do operacji o wysokim obciążeniu (przetwarzanie obrazów, parsowanie JSON) używaj ConcurrentDispatchQueueScheduler — RxSwift zachowa kolejność zdarzeń wewnątrz jednego Observable, ale różne Observable mogą być przetwarzane równolegle.
RxSwift w projektach iOS najczęściej stosuje się do łączenia UI z danymi przez reaktywne łańcuchy. RxCocoa dostarcza rx-rozszerzenia dla wszystkich standardowych komponentów UIKit: rx.tap dla UIButton, rx.text dla UITextField, rx.selectedRow dla UIPickerView. Pozwala to zrezygnować z @IBAction i delegatów, zastępując je deklaratywnymi subskrypcjami.
MVVM (Model-View-ViewModel) — wzorzec architektoniczny, w którym ViewModel zarządza stanem i logiką biznesową, a View subskrybuje reaktywne właściwości ViewModel. RxSwift idealnie wpisuje się w MVVM: ViewModel publikuje Observable lub Driver dla danych, View subskrybuje je przez bind. ViewModel nie ma referencji do View — zwraca Observable, które View konsumuje.
class LoginViewModel {
let email = BehaviorRelay<String>(value: "")
let password = BehaviorRelay<String>(value: "")
var isFormValid: Observable<Bool> {
return Observable
.combineLatest(email, password) {
!$0.isEmpty && $0.contains("@") && $1.count >= 6
}
}
}
// W ViewController:
viewModel.isFormValid
.bind(to: loginButton.rx.isEnabled)
.disposed(by: disposeBag)
Zapytania sieciowe — drugi pod względem częstotliwości obszar zastosowania RxSwift. URLSession.rx.response opakowuje zapytanie HTTP w Observable, który zwraca (response, data) przy sukcesie lub error przy niepowodzeniu. W połączeniu z Codable i operatorem map otrzymujemy zwartą reaktywną warstwę API: Observable<MyModel> bez callbacków i obsługi błędów w każdej metodzie. Przy błędzie sieci operator retry(3) automatycznie ponawia zapytanie trzykrotnie z wykładniczym opóźnieniem.
Combine — natywny framework Apple, wprowadzony w iOS 13. Rozwiązuje te same zadania co RxSwift: praca ze zdarzeniami asynchronicznymi przez Publisher, Subscriber i operatory. Jednak Combine ma kluczowe różnice: ścisła typizacja błędów przez typ Failure, wbudowane wsparcie Swift Concurrency (async/await) i integracja z SwiftUI przez @Published i ObservableObject.
RxSwift wygrywa w kompatybilności wstecznej — obsługuje iOS 8+, podczas gdy Combine wymaga iOS 13+. RxSwift ma bogatszy ekosystem rozszerzeń zewnętrznych (RxDataSources, RxGesture, RxAnimated) i szczegółową dokumentację operatorów. Dla projektów ze wsparciem starszych wersji iOS RxSwift pozostaje jedynym wyborem.
Dla nowych projektów z iOS 13+ Apple zaleca Combine. Jest ściśle zintegrowany z SwiftUI, ma mniejszy rozmiar binarny i oficjalne wsparcie. Jednak migracja istniejącego kodu RxSwift do Combine wymaga przepisania wszystkich Observable → Publisher, a ekosystem RxCocoa nie ma pełnego odpowiednika w Combine — UIKit nie ma natywnych Publisher dla wszystkich komponentów UI.
| Cecha | RxSwift | Combine |
|---|---|---|
| Minimalne iOS | iOS 8+ | iOS 13+ |
| Typ błędu | Error (dowolny) | Generic Failure |
| Rozszerzenia UI | RxCocoa (UIKit, AppKit) | @Published (SwiftUI) |
| Operatory | 400+ operatorów | ~100 operatorów |
| Swift Concurrency | Przez bridge | Natywne wsparcie |
| Status | Zewnętrzna | Apple oficjalnie |
Często zadawane pytania
Observable — źródło zdarzeń z leniwym wykonaniem. Subject jednocześnie jest Observable i Observer — możesz się na niego subskrybować i wysyłać do niego nowe zdarzenia ręcznie. Subject jest wygodny do integracji kodu imperatywnego z reaktywnymi łańcuchami.
RxSwift wybiera się przy potrzebie wsparcia iOS 11-12, przy istniejącej bazie kodu RxSwift lub przy potrzebie bogatego ekosystemu operatorów (400+ wobec ~100 w Combine). Dla nowych projektów na iOS 13+ preferowany jest Combine.
DisposeBag — kolekcja tokenów Disposable, automatycznie anulująca wszystkie subskrypcje przy deinicjalizacji właściciela. Bez DisposeBag subskrypcja tworzy silną referencję na domknięcie, co prowadzi do wycieku pamięci przy zniszczeniu UIViewController.
Używaj catchError do zastąpienia błędu wartością domyślną, retry do ponownego wykonania Observable, materialize do przekształcenia błędu w zdarzenie. W warstwie UI Driver i Signal nie przekazują błędów — są obsługiwane wewnętrznie.
Driver — to specjalny typ Observable, gwarantujący wykonanie na MainScheduler, brak błędów i współdzielenie zasobów. Driver gwarantuje, że aktualizacje UI odbywają się na głównym wątku. Observable nie daje takich gwarancji — wymaga observeOn.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również