Как работать с реактивным кодом в iOS на примере Combine

Пожалуй, каждый iOS-разработчик видел в требованиях вакансий «знание фреймворков RxSwift, RxCocoa». Эти инструменты основаны на концепции реактивного программирования.
Реактивное программирование, как следует из названия, основано на реакции на событие: пользователь взаимодействует с интерфейсом и ждёт реакцию от приложения. Этот подход популярен в фронтенд-разработке, в том числе на iOS.
Мы в Surf долгое время избегали «реактивщины» в приложениях. Во-первых, это лишние зависимости. Во-вторых, подобные библиотеки несут в себе не только преимущества, но и проблемы с дебагом, сложностью поддержки кода и так далее.
Однако с выходом Combine и SwiftUI, мы решили начать внедрять реактивный подход в наши приложения. Благо, теперь не нужны сторонние решения: хватит того, что предоставляет Apple. Давайте посмотрим, как можно работать с реактивным кодом.
Главные элементы Combine, с которыми происходит работа:
1) Publisher — издатель
Протокол, указывающий, что тип может передавать последовательность значений со временем. Publisher предоставляет данные только подписчику (Subscriber) и делает это, когда данные становятся доступны. Без подписки Publisher не активен.
Publisher описывается двумя ассоциированными типами: <Output, Failure>
Output
— тип выдаваемых значенийFailure
— тип возможной ошибки. Если ошибок быть не может, используется Never.
2) Subscriber — подписчик
Отвечает за запрос и получение данных от издателя, а также за обработку ошибок.
Имеет типы <Input, Failure>
:
Input
— тип входных данныхFailure
— тип ошибки
Subscriber сам инициирует запрос и управляет объёмом поступающих данных. Основные способы обработки:
sink(receiveCompletion:receiveValue:)Принимает два замыкания: первое вызывается при завершении (успешно или с ошибкой); второе — при получении значений.
assign(to:on:)Присваивает полученные значения свойству объекта по
keyPath
.
3) Operators — операторы
Методы, преобразующие данные и потоки. Операторы — это промежуточное звено между издателем и подписчиком. С их помощью строятся цепочки обработки, трансформации и фильтрации данных.
4) Subjects — субъекты
Особый вид Publisher. Объекты, реализующие этот протокол, могут отправлять значения подписчикам через метод .send(_).
Subjects полезны для интеграции императивного кода: позволяют вручную вставлять значения в поток.
Управление подпиской
Publisher продолжает отправку до завершения или ошибки. Если подписка больше не нужна, её можно отменить с помощью метода cancel(). Все подписчики реализуют протокол Cancellable.
Пример использования Combine с URLSession
var cancellable = Set<AnyCancellable>()
func sendURLRequest(_ urlRequest: URLRequest) {
URLSession.shared.dataTaskPublisher(for: urlRequest) // 1
.map(\.data) // 2
.decode(type: LocationModel.self, decoder: JSONDecoder()) // 3
.mapError { $0.toServerError() ?? .unidentifiedError } // 4
.receive(on: DispatchQueue.main) // 5
.sink { completion in // 6
print("finished stream with: \(completion)")
} receiveValue: { value in
print("receive value: \(value)")
}
.store(in: &cancellable) // 7
}
Что происходит:
Создание
Publisher
— оборачивает загрузку данных по URL.map(.data
) — извлекает Data из ответа.decode
— преобразует Data в модель LocationModel.mapError
— конвертирует ошибку в нужный формат.receive(on:)
— переключает поток на нужную очередь.sink
— обрабатывает завершение и полученные данные.store(in:)
— сохраняет подписку, чтобы поток не завершился сразу.
Такой подход делает код более читаемым, управляемым и реактивным.
Больше про iOS-разработку — в нашем Telegram-канале Surf Mobile Team. Кейсы, лучшие практики, новости индустрии, анонсы наших мероприятий и вакансий.