Programación reactiva en Swift: Combine y RxSwift
La programación reactiva es un paradigma orientado a flujos de datos asíncronos y la propagación de cambios. En el ecosistema Swift (iOS/macOS), las herramientas principales para implementar este paradigma son el framework Combine (integrado en el SDK de Apple) y la biblioteca de terceros RxSwift. Ambas herramientas permiten describir declarativamente el manejo de eventos, reduciendo la cantidad de funciones callback y haciendo el código más legible y predecible.
En este artículo, exploraremos los conceptos clave de la programación reactiva, compararemos Combine y RxSwift, y proporcionaremos ejemplos prácticos para iOS y macOS.
Conceptos principales
En el enfoque reactivo subyacen tres entidades:
- Publisher (Observable) — fuente de datos que genera eventos (valores, errores, finalización).
- Subscriber (Observer) — consumidor que reacciona a los eventos.
- Operator — función que transforma el flujo de datos (filtrado, transformación, combinación).
Ejemplo de un Publisher simple en Combine:
import Combine
let publisher = Just("¡Hola, Mundo Reactivo!")let subscription = publisher.sink { value in print(value)}// Salida: ¡Hola, Mundo Reactivo!Combine vs RxSwift: comparación
Ambos frameworks resuelven tareas similares, pero tienen diferencias:
- Combine — framework nativo de Apple (iOS 13+, macOS 10.15+). Utiliza tipado estricto y se integra con SwiftUI y URLSession.
- RxSwift — biblioteca multiplataforma con una gran comunidad. Soporta versiones anteriores de iOS (desde iOS 8).
- Rendimiento: Combine está optimizado para Apple Silicon, RxSwift puede ser más flexible en escenarios personalizados.
Ejemplos de código para iOS/macOS
1. Manejo reactivo de un campo de texto (Combine + UIKit)
Nos suscribimos a los cambios de texto en un UITextField:
import UIKitimport Combine
class ViewController: UIViewController { @IBOutlet weak var textField: UITextField! private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: textField) .compactMap { ($0.object as? UITextField)?.text } .debounce(for: .milliseconds(500), scheduler: RunLoop.main) .sink { text in print("El usuario ingresó: \\(text)") // Aquí se puede hacer una solicitud de red o actualizar la UI } .store(in: &cancellables) }}2. Combinación de múltiples flujos (RxSwift para macOS)
Ejemplo de combinación de dos secuencias observables:
import RxSwift
let disposeBag = DisposeBag()
let firstObservable = Observable.of(1, 2, 3)let secondObservable = Observable.of("A", "B", "C")
Observable.zip(firstObservable, secondObservable) .subscribe(onNext: { (number, letter) in print("\\(number) -> \\(letter)") }) .disposed(by: disposeBag)
// Salida:// 1 -> A// 2 -> B// 3 -> C3. Manejo de una solicitud de red usando Combine
Usamos URLSession para cargar datos:
import Combineimport Foundation
struct Post: Decodable { let id: Int let title: String}
func fetchPosts() -> AnyPublisher<[Post], Error> { let url = URL(string: "https://jsonplaceholder.typicode.com/posts")! return URLSession.shared.dataTaskPublisher(for: url) .map(\\.data) .decode(type: [Post].self, decoder: JSONDecoder()) .eraseToAnyPublisher()}