Action dan BindingTarget di ReactiveSwift

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

:

Action

RxSwiftCommunity/Action




All Articles