Action et liaisonTarget dans ReactiveSwift

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()
        }
    }
}

:

Action

RxSwiftCommunity/Action




All Articles