-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 2. Drive.
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
Statechanges 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.
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 holds State and mutates it in response to Action. The same as ViewModel in MVVM.
typealias BookDetailDriving = BookDetailState & BookDetailActionfinal 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() {
// 1. Mutate
guard let value = dataRelay.value?.toggleLike() else { return }
dataRelay.accept(value)
// 2. Persist
api.updateBookDetails(value)
.subscribe()
.disposed(by: bag)
// 3. Track
analytics.track(.likeUpdate(value.like, id: value.id))
}
private func bind() {
// 4. Fetch initial data
api.fetchBookDetails(forBookId: id)
.unwrap()
.compactMap(BookDetailData.init)
.bind(onNext: dataRelay.accept)
.disposed(by: bag)
}
}Now Driver has the following responsibilities:
• Fetch initial data from cloud (4) and update it when needed (2).
• Perform changes to internal state and store it.
• Track events to analytics
Even though all dependencies are used as protocols. Driver can have less responsibility. I will show you how to separate it with Binder.
Next I will show how to bind our ViewController with the Driver.