Olá, Habr!
Meu nome é Igor, sou o chefe do departamento móvel da AGIMA. Nem todo mundo mudou de ReactiveSwift / Rxswift para Combine ainda? Então hoje eu vou falar sobre a experiência do uso de tais conceitos como o ReactiveSwift Actione BindingTargete quais as tarefas que podem ser resolvidos com a ajuda deles. Noto imediatamente que para RxSwift os mesmos conceitos existem no formulário RxActione Binder. No artigo, consideraremos exemplos no ReactiveSwift e no final mostrarei como tudo parece igual no RxSwift.
Espero que você já saiba o que é programação reativa e tenha experiência com ReactiveSwift ou RxSwift.
Digamos que temos uma página de produto e um botão Adicionar aos favoritos. Quando o pressionamos, o carregador começa a girar em vez dele e, como resultado, o botão fica cheio ou não. Provavelmente, teremos algo assim no ViewController (usando a arquitetura MVVM).
let favoriteButton = UIButton()
let favoriteLoader = UIActivityIndicatorView()
let viewModel: ProductViewModel
func viewDidLoad() {
...
favoriteButton.reactive.image <~ viewModel.isFavorite.map(mapToImage)
favoriteLoader.reactive.isAnimating <~ viewModel.isLoading
//
favoriteButton.reactive.isHidden <~ viewModel.isLoading
favoriteButton.reactive.controlEvents(.touchUpInside)
.take(duringLifetimeOf: self)
.observeValues { [viewModel] _ in
viewModel.toggleFavorite()
}
}E no viewModel:
lazy var isFavorite = Property(_isFavorite)
private let _isFavorite: MutableProperty<Bool>
lazy var isLoading = Property(_isLoading)
private let _isLoading: MutableProperty<Bool>
func toggleFavorite() {
_isLoading.value = true
service.toggleFavorite(product).startWithResult { [weak self] result in
self._isLoading.value = false
switch result {
case .success(let isFav):
self?.isFavorite.value = isFav
case .failure(let error):
// do somtething with error
}
}
} , MutableProperty «» , . Action . «» . Action 2- : SignalProducer apply BindingTarget( ). , viewModel :
let isFavorite: Property<Bool>
let isLoading: Property<Bool>
private let toggleAction: Action<Void, Bool, Error>
init(product: Product, service: FavoritesService = FavoriteServiceImpl()) {
toggleAction = Action<Void, Bool, Error> {
service.toggleFavorite(productId: product.id)
.map { $0.isFavorite }
}
isFavorite = Property(initial: product.isFavorite, then: toggleAction.values)
isLoading = toggleAction.isExecuting
}
func toggleFavorite() {
favoriteAction.apply().start()
}? , . , Action
Action SignalProducer ( RxSwift: SignalProducer — , Signal — ). Action , execute , SignalProducer.

( !) .
final class Action<Input, Output, Error> {
let values: Signal<Output, Never>
let errors: Signal<Error, Never>
let isExecuting: Property<Bool>
let isEnabled: Property<Bool>
var bindingTarget: BindingTarget<Input>
func apply(_ input: Input) -> SignalProducer<Output, Error> {...}
init(execute: @escaping (T, Input) -> SignalProducer<Output, Error>)
} ? values Action errors— . isExecuting , ( ). , values errors Never «», . isEnabled- Action / , . , 10 . , «» Action , , , , :)
1: apply SignalProducer values , errors, isExecuting , Action
2: Action . Action , . , , Action ( RxSwift).
SignalProducer, favoriteAction.values , favoriteAction.errors
2- Action BindingTarget viewModel toggleFavorite :
let toggleFavorite: BindingTarget<Void> = favoriteAction.bindingTarget
viewModel.toggleFavorite <~ button.reactive.controlEvents(.touchUpInside) . . BindingTarget.
E, , : SignalProducer, , - . , SignalProducer Signal Disposable dispose(). input , SignalProducer Action disposable .
BindingTarget? BindingTarget ,
, Lifetime(, ). , Observer MutableProperty BindingTarget.
. , BindingTarget— , «» :
isLoadingSignal
.take(duringLifetimeOf: self)
.observe { [weak self] isLoading in
isLoading ? self?.showLoadingView() : self?.hideLoadingView()
}:
self.reactive.isLoading <~ isLoadingSignal— , .
isLoading ( ):
extension Reactive where Base: ViewController {
var isLoading: BindingTarget<Bool> {
makeBindingTarget { (vc, isLoading) in
isLoading ? vc.showLoadingView() : vc.hideLoadingView()
}
}
}, makeBindingTarget , . KeyPath ( ):
var isLoading = false
...
reactive[\.isLoading] <~ isLoadingSignal BindingTarget ReactiveCocoa , , , , 99% .
Action «» ViewModel . BindingTarget , , , , :)
RxSwift
ViewController:
viewModel.isFavorite
.map(mapToImage)
.drive(favoriteButton.rx.image())
.disposed(by: disposeBag)
viewModel.isLoading
.drive(favoriteLoader.rx.isAnimating)
.disposed(by: disposeBag)
viewModel.isLoading
.drive(favoriteButton.rx.isHidden)
.disposed(by: disposeBag)
favoriteButton.rx.tap
.bind(to: viewModel.toggleFavorite)
.disposed(by: disposeBag)ViewModel
let isFavorite: Driver<Bool>
let isLoading: Driver<Bool>
let toggleFavorite: AnyObserver<Void>
private let toggleAction = Action<Void, Bool>
init(product: Product, service: FavoritesService = FavoriteServiceImpl()) {
toggleAction = Action<Void, Bool> {
service.toggleFavorite(productId: product.id)
.map { $0.isFavorite }
}
isFavorite = toggleAction.elements.asDriver(onErrorJustReturn: false)
isLoading = toggleAction.executing.asDriver(onErrorJustReturn: false)
toggleFavorite = toggleAction.inputs
}Binder
extension Reactive where Base: UIViewController {
var isLoading: Binder<Bool> {
Binder(self.base) { vc, value in
value ? vc.showLoadingView() : vc.hideLoadingView()
}
}
}: