Acción y BindingTarget en ReactiveSwift

¡Hola, Habr!

Mi nombre es Igor, soy el jefe del departamento de móviles de AGIMA. ¿No todos han cambiado de ReactiveSwift / Rxswift a Combine todavía? Entonces hoy voy a hablar de la experiencia del uso de conceptos tales como la ReactiveSwift Actiony BindingTargety qué tareas se puede resolver con su ayuda. Observo de inmediato que para RxSwift existen los mismos conceptos en la forma RxActiony Binder. En el artículo, consideraremos ejemplos en ReactiveSwift y al final mostraré cómo todo se ve igual en RxSwift.

Espero que ya sepas qué es la programación reactiva y tengas experiencia con ReactiveSwift o RxSwift.

Digamos que tenemos una página de producto y un botón para agregar a favoritos. Cuando lo presionamos, el cargador comienza a girar en lugar de él y, como resultado, el botón se llena o no. Lo más probable es que tengamos algo como esto en ViewController (usando la arquitectura 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()
     }
}

Y en el 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()
        }
    }
}

:

Action

RxSwiftCommunity/Action




All Articles