RxSwift is a reactive programming library for iOS that implements the Observable pattern and functional operators for working with asynchronous data streams. It ports ReactiveX (Rx) concepts to Swift, providing a unified API for handling events from UI, network requests, databases and timers. According to ReactiveX, 2025, the library is used in more than 60,000 projects on GitHub and remains the standard for reactive programming in Swift until the advent of native Combine. RxSwift replaces delegates, closures and notifications with a single event processing chain.
Key Takeaways
RxSwift is the Swift port of the ReactiveX (Rx) library created by Microsoft for .NET in 2012. The core idea of Rx is to represent any asynchronous data source as an Observable sequence to which functional operators can be applied. In iOS development, RxSwift is used to bind UI events (button taps, text input, gestures) to application logic without explicit delegates and target-action.
The library consists of three modules: RxSwift (core — Observable, Operator, Scheduler), RxCocoa (UIKit integration — rx extensions for UIButton, UITextField, UITableView) and RxRelay (Subject without terminal events). This separation allows using the core in server-side Swift and tests, connecting the UI wrapper only for iOS applications.
According to the Stack Overflow, 2025 survey, RxSwift is among the top 5 most used third-party libraries for iOS. The main reason for its popularity is the uniform handling of all asynchronous patterns: network requests via URLSession, animations via UIViewPropertyAnimator, notifications via NotificationCenter and delegates via DelegateProxy — all of this boils down to an Observable with a predictable lifecycle.
Reactive programming is a paradigm in which the program reacts to data changes rather than polling for them. Instead of writing “get data → process → update UI”, the developer describes a transformation chain: “when data changes, apply filter, then map, then update UI”. RxSwift implements this paradigm through Observable chains with lazy execution — nothing happens until a subscriber appears.
Observable is the fundamental RxSwift type representing a sequence of events over time. Observable can send three types of events: next (new value), error (error terminating the stream) and completed (successful completion). There are finite Observable (complete after sending all values) and infinite Observable (e.g. UI events — never complete).
The lifecycle of an Observable includes three stages: creation (create, just, from), transformation (operators map, filter, flatMap) and subscription (subscribe). Without a subscription, Observable does not perform any actions — it is a lazy sequence. After subscription, Observable starts sending events to the subscriber until it completes or the subscriber cancels the subscription via Disposable.
import RxSwift
// Creating Observable from array
let numbers = Observable.from([1, 2, 3, 4, 5])
// Transformation through operators
let squared = numbers
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
// Subscription with event handling
let disposable = squared
.subscribe(onNext: { print($0) },
onError: { print("Error: \($0)") },
onCompleted: { print("Done") })
Subject is a type that is both an Observable (can be subscribed to) and an Observer (can send events to). RxSwift provides four types of Subject: PublishSubject (only new events), BehaviorSubject (with an initial value), ReplaySubject (buffers the last N events) and AsyncSubject (only the last value before completion). Subject is useful for integrating imperative code (closures, delegates) into a reactive chain.
Disposable is a subscription cancellation token. When the subscriber no longer wants to receive events, it calls disposable.dispose(). In practice, DisposeBag is used: a collection of Disposable that automatically cancels all subscriptions upon deinitialization of the owner (e.g. UIViewController). This prevents memory leaks that are inevitable with manual subscription management.
| Subject Type | Initial Value | Replays to Subscriber | Use Case |
|---|---|---|---|
| PublishSubject | No | Only new events | UI events, rare notifications |
| BehaviorSubject | Yes | Last + new | State, data stream |
| ReplaySubject | No | Buffer N + new | History caching |
| AsyncSubject | No | Only last | Computational tasks |
| PublishRelay | No | Only new | Without error/completed |
Operators in RxSwift are functions that take one Observable and return another Observable, transforming the data stream. By combining operators, the developer builds declarative processing chains without intermediate variables and race conditions. All operators are lazy: the chain is built during description and executed upon subscription.
Transformation operators change each event in the stream. map applies a function to each element, flatMap unwraps Observable from each element into a single flat stream, scan accumulates intermediate results (similar to reduce but emitting each step). buffer groups elements by time or count, window divides the stream into nested Observable windows.
// Example of operator chain: search with 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)
Filtering operators only pass events that satisfy a condition. filter filters by predicate, distinctUntilChanged only passes values different from the previous one, take and takeWhile limit the number of events. skip and skipWhile skip the first N events or events until a condition is met. For error handling, catchError (intercept and substitute) and retry (retry sequence on error) are used.
Combination operators combine multiple Observables into one. combineLatest merges the latest values from multiple streams into a tuple, zip pairs elements with the same indices, merge combines multiple streams into one in the order of event arrival. withLatestFrom combines an event from the main stream with the latest value from another stream — useful for UI events with current state.
| Category | Operator | Description |
|---|---|---|
| Transformation | map | Transforms each value through a function |
| Transformation | flatMap | Unwraps nested Observables into a flat stream |
| Filtering | filter | Passes values by predicate |
| Filtering | distinctUntilChanged | Passes only changed values |
| Combination | combineLatest | Combines latest values from 2+ streams |
| Combination | zip | Pairs elements by index |
| Control | debounce | Delays events until a pause in the stream |
| Control | take | Takes first N events and completes the stream |
Scheduler in RxSwift is an abstraction over the execution thread (or queue). Scheduler determines on which thread event creation, transformation and subscription are executed. Unlike GCD, where the developer explicitly specifies DispatchQueue, RxSwift uses two parameters: subscribeOn (on which thread the Observable executes) and observeOn (on which thread the subscriber is called).
MainScheduler executes code on the main thread — required for UIKit updates (all UI changes must happen on the main thread). SerialDispatchQueueScheduler wraps a serial GCD queue, ConcurrentDispatchQueueScheduler wraps a concurrent one. For background tasks, ConcurrentDispatchQueueScheduler with qos: .background or .utility is used.
// Typical pattern: background work + UI on main
apiService.fetchData()
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.observeOn(MainScheduler.instance)
.subscribe(onNext: { data in
self.tableView.reloadData()
self.loadingIndicator.stopAnimating()
})
.disposed(by: disposeBag)
Proper use of Scheduler prevents UI blocking during network requests and heavy computations. subscribeOn affects the entire upstream (all operators before observeOn execute on the specified scheduler). observeOn switches the downstream — all operators after observeOn execute on the specified scheduler. A chain can have multiple observeOn for switching between threads at different transformation stages.
SerialDispatchQueueScheduler guarantees sequential event processing, which is important for thread safety of shared resources. For high-load operations (image processing, JSON parsing) use ConcurrentDispatchQueueScheduler — RxSwift will preserve event order within one Observable, but different Observables may be processed in parallel.
RxSwift in iOS projects is most often used for binding UI with data through reactive chains. RxCocoa provides rx extensions for all standard UIKit components: rx.tap for UIButton, rx.text for UITextField, rx.selectedRow for UIPickerView. This allows abandoning @IBAction and delegates, replacing them with declarative subscriptions.
MVVM (Model-View-ViewModel) is an architectural pattern in which ViewModel manages state and business logic, and View subscribes to reactive properties of the ViewModel. RxSwift fits perfectly with MVVM: ViewModel publishes Observable or Driver for data, View subscribes to them via bind. ViewModel has no reference to View — it returns Observable that View consumes.
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
}
}
}
// In ViewController:
viewModel.isFormValid
.bind(to: loginButton.rx.isEnabled)
.disposed(by: disposeBag)
Network requests are the second most common use case for RxSwift. URLSession.rx.response wraps an HTTP request into an Observable that returns (response, data) on success or error on failure. In combination with Codable and the map operator, a compact reactive API layer is obtained: Observable<MyModel> without callbacks and error handling in each method. On network error, the retry(3) operator automatically retries the request three times with exponential backoff.
Combine is Apple’s native framework introduced in iOS 13. It solves the same tasks as RxSwift: working with asynchronous events through Publisher, Subscriber and operators. However, Combine has key differences: strict error typing via the Failure type, built-in Swift Concurrency support (async/await) and integration with SwiftUI through @Published and ObservableObject.
RxSwift wins in backward compatibility — it supports iOS 8+, while Combine requires iOS 13+. RxSwift has a richer ecosystem of third-party extensions (RxDataSources, RxGesture, RxAnimated) and detailed operator documentation. For projects supporting older iOS versions, RxSwift remains the only choice.
For new projects with iOS 13+, Apple recommends Combine. It is tightly integrated with SwiftUI, has a smaller binary size and official support. However, migrating existing RxSwift code to Combine requires rewriting all Observable → Publisher, and the RxCocoa ecosystem does not have a full Combine equivalent — UIKit does not have native Publisher for all UI components.
| Feature | RxSwift | Combine |
|---|---|---|
| Minimum iOS | iOS 8+ | iOS 13+ |
| Error Type | Error (any) | Generic Failure |
| UI Extensions | RxCocoa (UIKit, AppKit) | @Published (SwiftUI) |
| Operators | 400+ operators | ~100 operators |
| Swift Concurrency | Via bridge | Native support |
| Status | Third-party | Apple official |
Frequently Asked Questions
Observable is a source of events with lazy execution. Subject is both an Observable and an Observer — you can subscribe to it and send new events to it manually. Subject is useful for integrating imperative code into reactive chains.
RxSwift is chosen when iOS 11-12 support is needed, when there is an existing RxSwift codebase, or when a rich operator ecosystem is required (400+ vs ~100 in Combine). For new projects on iOS 13+, Combine is preferred.
DisposeBag is a collection of Disposable tokens that automatically cancels all subscriptions upon owner deinitialization. Without DisposeBag, the subscription creates a strong reference to the closure, leading to memory leaks when UIViewController is destroyed.
Use catchError to replace an error with a default value, retry to retry Observable execution, materialize to transform the error into an event. In the UI layer, Driver and Signal do not pass errors — they are handled internally.
Driver is a special type of Observable that guarantees execution on MainScheduler, absence of errors and resource sharing. Driver ensures that UI updates happen on the main thread. Observable does not provide such guarantees — observeOn is required.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also