Bonjour, Habr!
Je m'appelle Igor, je suis responsable du département mobile chez AGIMA. Tout le monde n'est pas encore passé de ReactiveSwift / Rxswift à Combine? Puis aujourd'hui, je parlerai de l'expérience de l'utilisation de concepts tels que le ReactiveSwift Actionet des BindingTargettâches qui peuvent être résolues avec leur aide. Je note tout de suite que pour RxSwift les mêmes concepts existent dans la forme RxActionet Binder. Dans l'article, nous examinerons des exemples sur ReactiveSwift et à la fin je montrerai comment tout se ressemble sur RxSwift.
J'espère que vous savez déjà ce qu'est la programmation réactive et que vous avez de l'expérience avec ReactiveSwift ou RxSwift.
Disons que nous avons une page produit et un bouton Ajouter aux favoris. Lorsque nous appuyons dessus, le chargeur commence à tourner à sa place et, par conséquent, le bouton est rempli ou non. Très probablement, nous aurons quelque chose comme ça dans le ViewController (en utilisant l'architecture 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()
}
}Et dans la vueModèle:
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()
}
}
}: