Rx_Best_Practices
RxSwift Best Practises
Listing overall learnings of RxSwift do's and don'ts for writing good reactive code which is easily discernible and succinct enough to get the job done.
Architecture
Bla Bla Car app for RxSwift + MVVM
Medium | article from Bla Bla car app
Code structure
Input & Output
We use structs inside our classes with explicit definition called "Input" and "Output"
Eg.
extension AnyViewModel {
public struct Input {
public let newAppName: AnyObserver<String>
public let currentAppSelection: AnyObserver<App>
public let navigateAppsList: AnyObserver<Void>
}
public struct Output {
public let navigationContext: Driver<NavigationContext>
public let dismiss: Driver<Void>
}
}
ViewState Architecture
We also have ViewState Architecture which gets subscribed from Rx point of view to have uninterrupted view state updates. Conformance to protocol ViewStateDrivable
is required.
extension AnyViewModel {
public struct Output: ViewStateDrivable {
public let viewState: Driver<ViewState<AppSelectionState, Never, Never, Never>>
public let navigationContext: Driver<NavigationContext>
}
}
Syntactic Sugar
Where writing less and concise is the way of life 😌
Unretained Self
When referencing self in a closure like we always want to have weak reference in order to avoid retaining the reference thus creating a retain cycle which won't be released automatically by ARC (Automatic Reference Counting) mechanism.
Example
Old way
let closeButtonItem = UIBarButtonItem(.close)
closeButtonItem
.rx
.tap
.asObservable()
.subscribe(onNext: { [weak self], _ in
self?.dismiss(animated: true)
})
.disposed(by: self.disposeBag)
New Way
let closeButtonItem = UIBarButtonItem(.close)
closeButtonItem
.rx
.tap
.asObservable()
.withUnretained(self)
.subscribe(onNext: { owner, _ in
owner.dismiss(animated: true)
})
.disposed(by: self.disposeBag)
Unwrapping in chain
You may sometimes get an optional value as a return type and it is not that good looking when reading the code where there is optional binding involved in that Rx Chain.
Luckily to make the code more readable you could extend Rx to provide us some utilities which does the unwrapping for us.
Old Code: Though I'm not sure guard else statement in RxCocoa eg. would suffice empty FormFieldState()
object.
tableView
.rx
.itemDeleted
.asObservable()
.map { [weak dataSource] indexPath in
guard let formField = dataSource?[indexPath] else { return FormFieldState() } // Optional unwrap
return formField
}
.bind(to: viewModel.input.deleteFormField)
.disposed(by: disposeBag)
New Code
tableView
.rx
.itemDeleted
.asObservable()
.map { [weak dataSource] indexPath in
return dataSource?[indexPath]
}
.unwrap()
.bind(to: viewModel.input.deleteFormField)
.disposed(by: disposeBag)
Extension code
extension ObservableType where Element: ExpressibleByNilLiteral {
/// Unwraps an observable and emits the value of a non nil value to the Rx stream, or stops the stream if the value is nil.
public func unwrap<T>() -> Observable<T> where Element == T? {
return flatMap { Observable.from(optional: $0) }
}
}
Right tool
Choosing the right tool is always the hardest job one might argue. Should we go for merge or flatMapLatest or just flatMap. Rx provides lots of tools under its belt and it can be overwhelming sometimes to pick one tool over another.
UI
For UI elements always go for Rx Traits as they are basically Subjects but they don't error out.
Relays are a good example as they are ran on main thread so that the UI work is performed on highest priority and would not have undesirable side effect from it.
View Model
I'm not 100% sure but I believe we extensively use Subjects in our ViewModel with structure of "Input" and "Output" to further differentiate who does what and its expectations.