Реактивное программирование в Swift: Combine и RxSwift для iOS/macOS

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Реактивное программирование в Swift: Combine и RxSwift

Реактивное программирование — это парадигма, ориентированная на асинхронные потоки данных и распространение изменений. В экосистеме Swift (iOS/macOS) основными инструментами для реализации этой парадигмы являются фреймворк Combine (встроенный в Apple SDK) и сторонняя библиотека RxSwift. Оба инструмента позволяют декларативно описывать обработку событий, уменьшая количество callback-функций и делая код более читаемым и предсказуемым.

В этой статье мы рассмотрим ключевые концепции реактивного программирования, сравним Combine и RxSwift, а также приведем практические примеры для iOS и macOS.

Основные концепции

В основе реактивного подхода лежат три сущности:

  • Publisher (Observable) — источник данных, который генерирует события (значения, ошибки, завершение).
  • Subscriber (Observer) — потребитель, который реагирует на события.
  • Operator — функция, преобразующая поток данных (фильтрация, трансформация, комбинирование).

Пример простого Publisher в Combine:

import Combine

let publisher = Just("Hello, Reactive World!")
let subscription = publisher.sink { value in
    print(value)
}
// Вывод: Hello, Reactive World!

Combine vs RxSwift: сравнение

Оба фреймворка решают схожие задачи, но имеют различия:

  • Combine — нативный фреймворк Apple (iOS 13+, macOS 10.15+). Использует строгую типизацию и интегрируется с SwiftUI и URLSession.
  • RxSwift — кроссплатформенная библиотека с большим комьюнити. Поддерживает более старые версии iOS (начиная с iOS 8).
  • Производительность: Combine оптимизирован для Apple Silicon, RxSwift может быть более гибким в кастомных сценариях.

Примеры кода для iOS/macOS

1. Реактивная обработка текстового поля (Combine + UIKit)

Подписываемся на изменения текста в UITextField:

import UIKit
import 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("Пользователь ввел: \(text)")
                // Здесь можно сделать сетевой запрос или обновить UI
            }
            .store(in: &cancellables)
    }
}

2. Комбинирование нескольких потоков (RxSwift для macOS)

Пример объединения двух observable-последовательностей:

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)

// Вывод:
// 1 -> A
// 2 -> B
// 3 -> C

3. Обработка сетевого запроса с использованием Combine

Используем URLSession для загрузки данных:

import Combine
import 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()
}

// Подписка
var cancellables = Set<AnyCancellable>()
fetchPosts()
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Загрузка завершена")
        case .failure(let error):
            print("Ошибка: \(error)")
        }
    }, receiveValue: { posts in
        print("Получено \(posts.count) постов")
        // Обновление UI
    })
    .store(in: &cancellables)

Преимущества реактивного подхода в Swift

  • Уменьшение состояния: данные передаются по потоку, минимизируя общие переменные.
  • Читаемость: цепочки операторов заменяют вложенные callback-функции.
  • Управление памятью: Combine использует

Рекомендаций