Skip to content

Chapter 2. Drive.

Dmitriy Shulzhenko edited this page Oct 11, 2020 · 25 revisions

State

Let's begin with the State. It can be a shared app component state or a simple view state.

I will not explain why state management is important to write better organised code. Here I will focus on how I usually use it.

It is not possible to achieve perfect characteristics all of the times due to legacy Apple patters, but I hope that things will change over time with the help of Combine.

Rules for "perfect" State. :

  • It is Observable. Every other component knows about State changes and can react immediately - perform actions or side effects
  • It is immutable for the outside world. This means that other parts can only read it, observe and react, not to change directly.

Here is simple possible definition of State

import RxSwift
import RxCocoa

protocol BookDetailState {
    var data: Driver<BookDetailData> { get }
    var close: Signal<Void> { get }
}

Where data is used to display some cells and close as navigation trigger.

Action

State must be changed only through Actions. It is simply just has to pass information and trigger state change.

Primitive action may be like this one:

protocol BookDetailAction {
    func close()
    func toggleLikeState()
}

Driver

Driver holds State and mutates it in response to Action. The same as ViewModel in MVVM.

typealias BookDetailDriving = BookDetailState & BookDetailAction
final class BookDetailDriver: BookDetailDriving {
    let bag = DisposeBag()
    private let closeRelay = PublishRelay<Void>()
    private let dataRelay = BehaviorRelay<BookDetailData?>(value: nil)
    
    private let id: Int
    private let api: ApiProvider
    private let analytics: Analytics
    
    var data: Driver<BookDetailData> { dataRelay.unwrap().asDriver() }
    
    var closeSignal: Signal<Void> { closeRelay.asSignal() }
    
    init(id: Int,
         api: ApiProvider,
         analytics: Analytics) {
        self.id = id
        self.api = api
        self.analytics = analytics
        bind()
    }
    
    func close() {
        closeRelay.accept(())
    }
    
    func toggleLikeState() {
// Mutate and save `State`
        guard let value = dataRelay.value?.toggleLike() else { return }
        dataRelay.accept(value)
        analytics.track(.likeUpdate(value.like, id: value.id))
    }
    
    private func bind() {
        api.fetchBookDetails(forBookId: id)
            .unwrap()
            .compactMap(BookDetailData.init)
            .bind(onNext: dataRelay.accept)
            .disposed(by: bag)
    }
}

Next I will show how to bind our ViewController with the Driver.

Clone this wiki locally