Halo, Habr!
Nama saya Igor, saya adalah kepala departemen keliling di AGIMA. Belum semua orang beralih dari ReactiveSwift / Rxswift ke Combine? Kemudian hari ini saya akan berbicara tentang pengalaman penggunaan konsep seperti ReactiveSwift Actiondan BindingTargetdan tugas apa yang dapat diselesaikan dengan bantuan mereka. Saya segera mencatat bahwa untuk RxSwift konsep yang sama ada dalam bentuk RxActiondan Binder. Dalam artikel ini, kami akan mempertimbangkan contoh di ReactiveSwift dan pada akhirnya saya akan menunjukkan bagaimana semuanya terlihat sama di RxSwift.
Saya harap Anda sudah tahu apa itu pemrograman reaktif dan memiliki pengalaman dengan ReactiveSwift atau RxSwift.
Katakanlah kita memiliki halaman produk dan tombol tambahkan ke favorit. Saat kita menekannya, pemuat mulai berputar, bukan itu, dan akibatnya tombol menjadi terisi atau tidak. Kemungkinan besar, kita akan memiliki sesuatu seperti ini di ViewController (menggunakan arsitektur 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()
}
}Dan di 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()
}
}
}: