Rx in the real world
1 Rob Ciolli
Rx in the real world 1 Rob Ciolli 2 Rob Ciolli 3 Rob Ciolli - - PowerPoint PPT Presentation
Rx in the real world 1 Rob Ciolli 2 Rob Ciolli 3 Rob Ciolli The App 4 Rob Ciolli Quick architecture overview 5 Rob Ciolli MV - WTF 6 Rob Ciolli Model Simple, immutable data struct returned from DB or APIs 7 Rob Ciolli View UI
1 Rob Ciolli
2 Rob Ciolli
3 Rob Ciolli
4 Rob Ciolli
5 Rob Ciolli
6 Rob Ciolli
Simple, immutable data struct returned from DB or APIs
7 Rob Ciolli
UI layout as defined in storyboard file
8 Rob Ciolli
Regular Cocoa ViewController
protocol ViewControllerProtocol: class { ... associatedtype ViewModelType func recieve(viewModel: ViewModelType) }
9 Rob Ciolli
import UIKit protocol PresenterProtocol { associatedtype ViewControllerType: UIViewController func makeViewController() -> ViewControllerType }
10 Rob Ciolli
protocol ViewModelDelegateProtocol { } protocol ViewModelProtocol { associatedtype DelegateType //: ViewModelDelegateProtocol var delegate: DelegateType { get } init(delegate: DelegateType) } class BaseViewModel<T> : ViewModelProtocol { let delegate: T required init(delegate: T) { self.delegate = delegate } }
11 Rob Ciolli
12 Rob Ciolli
struct Beer { let id: Int let name: String let style: String let brewry: String let abv: Double let ibu: Double let description: String let image: String }
13 Rob Ciolli
14 Rob Ciolli
15 Rob Ciolli
let observable = Observable<String>.create { observer in
return Disposables.create() }
16 Rob Ciolli
let disposeBag = DisposeBag()
.subscribe(
) .disposed(by: disposeBag)
17 Rob Ciolli
Observable Operators
18 Rob Ciolli
import RxSwift protocol Datalayer { func requestAllBeers() -> Observable<[Beer]> }
19 Rob Ciolli
struct DetailPresenter { let beer: Beer init(beer: Beer) { self.beer = beer } } extension DetailPresenter: PresenterProtocol { ... } extension DetailPresenter: DetailViewModelDelegateProtocol { }
20 Rob Ciolli
protocol DetailViewModelDelegateProtocol: ViewModelDelegateProtocol { var beer: Beer { get } } class DetailViewModel: BaseViewModel<DetailViewModelDelegateProtocol> { let name = Variable<String?>("") ... required init(delegate: DetailViewModelDelegateProtocol) { super.init(delegate: delegate) name.value = beer.name ... } }
21 Rob Ciolli
22 Rob Ciolli
class DetailViewController: UIViewController, ViewControllerProtocol { ... let disposeBag = DisposeBag() @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var imageView: UIImageView!
super.viewDidLoad() viewModel.name.asObservable() .bindTo(nameLabel.rx.text) .disposed(by: disposeBag) viewModel.image.asObservable() .map { UIImage(named: $0)! } .bindTo(imageView.rx.image) .disposed(by: disposeBag) } }
23 Rob Ciolli
24 Rob Ciolli
Binding is too verbose Only displays one piece of data Not really reactive
25 Rob Ciolli
heaps more reactive
26 Rob Ciolli
27 Rob Ciolli
viewModel.name.asObservable() .bindTo(nameLabel.rx.text)
nameLabel.rx.text <- viewModel.name
28 Rob Ciolli
disposeBag.dispose([ nameLabel.rx.text <- viewModel.name, styleLabel.rx.text <- viewModel.style, brewryLabel.rx.text <- viewModel.brewry, ... ])
29 Rob Ciolli
infix operator <- func <- <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable { return variable.asObservable().bind(to: property) }
30 Rob Ciolli
extension DisposeBag { func dispose(_ disposables: [Disposable]) { disposables.forEach { [unowned self] disposable in self.insert(disposable) } } }
31 Rob Ciolli
protocol DetailViewModelDelegateProtocol: ViewModelDelegateProtocol { var beers: Observable<[Beer]> { get } ... }
32 Rob Ciolli
let next = Variable<()>() let index: Variable<Int> ... next.asObservable().subscribe(onNext: onNext) ... private func onNext() { index.value += 1 }
33 Rob Ciolli
34 Rob Ciolli
Observable.combineLatest(delegate.beers, index.asObservable(), resultSelector: selectBeer)
35 Rob Ciolli
extension UIButton { var rx_driveEnable: AnyObserver<Bool> { return UIBindingObserver(UIElement: self) { button, enabled in button.isUserInteractionEnabled = enabled }.asObserver() } }
36 Rob Ciolli
37 Rob Ciolli
capture user input validate input call api and handle response
38 Rob Ciolli
protocol SigninViewModelDelegateProtocol: ViewModelDelegateProtocol { func signin(username: String, password: String) -> Observable<Void> } class SigninViewModel: BaseViewModel<SigninViewModelDelegateProtocol> { ... let username = Variable<String?>(nil) let password = Variable<String?>(nil) let signInTapped = Variable<()>() let error = Variable<String?>(nil) let signedIn = Variable<()>()
39 Rob Ciolli
... signInTapped.asObservable() .subscribe(onNext: signin) .disposed(by: disposeBag) ... private func signin() { guard let username = username.value, let password = password.value else { return } delegate.signin(username: username, password: password) .subscribe(onNext: onSignedIn, onError: onSignInError) .disposed(by: disposeBag) } private func onSignedIn() { signedIn.value = () } private func onSignInError(_: Error) { error.value = "Error signing in" }
40 Rob Ciolli
var signInEnabled: Observable<Bool> { return Observable.combineLatest(username.asObservable(), password.asObservable(), resultSelector: inputIsValid) } private func inputIsValid(username: String?, password:String?) -> Bool { guard let username = username, let password = password else { return false } return !username.isEmpty && !password.isEmpty } }
41 Rob Ciolli
42 Rob Ciolli
filter list show detail view
43 Rob Ciolli
let searchVariable = Variable<String?>("") let itemTapped = Variable<Int>(-1) required init(delegate: ListViewModelDelegateProtocol) { ... itemTapped.asObservable() .skip(1) .subscribe(onNext: onItemTapped) .disposed(by: disposeBag) }
44 Rob Ciolli
45 Rob Ciolli
func filteredBeers() -> Observable<[Beer]> { let search = searchVariable.asObservable() return Observable.combineLatest(delegate.beers, search, resultSelector: filterBeers) } private func filterBeers(beers: [Beer], search: String?) -> [Beer] { guard let search = search else { return beers } return beers.filter { search.isEmpty || $0.name.lowercased().contains(search.lowercased()) } }
46 Rob Ciolli
private func onItemTapped(index: Int) { delegate.showDetail(beers: filteredBeers(), index: index) }
47 Rob Ciolli
viewModel.filteredBeers() .debounce(0.3, scheduler: MainScheduler.instance) .bind(to: tableView.rx.items( cellIdentifier: "Cell", cellType: UITableViewCell.self), curriedArgument: initialiseCell)
48 Rob Ciolli
49 Rob Ciolli
50 Rob Ciolli
/reactivex.io/
/rxmarbles.com/
/github.com/ReactiveX/RxSwift
51 Rob Ciolli
52 Rob Ciolli