哈Ha!
我叫Igor,我是AGIMA移动部门的负责人。并不是每个人都从ReactiveSwift / Rxswift切换到Combine吗?那么今天我就谈谈使用等概念ReactiveSwift的经验Action
和BindingTarget
什么任务都可以在他们的帮助来解决。我立即注意到,对于RxSwift,相同的概念还存在于RxAction
和的形式中Binder
。在本文中,我们将考虑ReactiveSwift上的示例,最后,我将展示RxSwift上的一切看起来如何。
我希望您已经知道什么是反应式编程,并具有ReactiveSwift或RxSwift的经验。
假设我们有一个产品页面和一个“添加到收藏夹”按钮。当我们按下它时,加载器开始旋转而不是旋转,结果,按钮变为填充或不填充。最有可能的是,我们将在ViewController中使用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()
}
}
并在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()
}
}
}
: