Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxSwift - Debunking the Myth of Hard

RxSwift - Debunking the Myth of Hard

The slides for "RxSwift - Debunking the Myth of Hard" by Shai Mishali as presented in:

Gett iOS Tech Meetup - Tel Aviv, Israel - January 30, 2018
NextDoor Conf - San Jose, California - June 6, 2018
SwiftConf 2018 - Köln, Germany - September 20, 2018
Mobile Unplugged 2018 - Cluj-Napoca, Romania - Oct 11, 2018

Youtube (SwiftConf '18): https://www.youtube.com/watch?v=GdvLP0ZAhhc
Twitter: https://twitter.com/freak4pc
GitHub: https://github.com/freak4pc

Shai Mishali

October 11, 2018
Tweet

More Decks by Shai Mishali

Other Decks in Programming

Transcript

  1. About Me Shai Mishali @freak4pc iOS Tech Lead @ Gett

    Open Source = ❤ Won some hackathons Battlehack Tel Aviv 2014
 Battlehack World Finals 2014 Ford Dev Challenge 2015 iOS Team @ RW
  2. Agenda • What is Rx & Why you’d want to

    use it • RxSwift’s Building Blocks • RxCocoa: RxSwift’s UI companion • Debunking some Myths • Where to go next? And of course … An absurd amount of Memes
  3. Observers / Subscribers the observer pattern “A software design pattern

    in which an object (Observable) maintains a list of Observers and notifies them automatically of changes.” isReady
 Observable View 1
 Observer Some
 Manager
 Observer Activity
 Indicator Observer View 2 Observer
  4. What is Rx? - Focusing on Data Flow and Defined

    Behavior to
 drive your application - A new concept to most developers - High learning curve - This lecture won’t make you an
 Rx expert - Most likely will bend your brain a bit until it “clicks” Be Patient !
  5. WHY USE RX? • Provides a unified API for dealing

    with asynchronous actions (e.g. Observable). • Provides a mechanism to Reactively handle and manipulate Values over Time. • Lets you focus on writing Declarative and Expected Code that defines behavior instead of imperative code. Meaning, lets values and data drive the app instead of “manually” refreshing / resetting state. • Encourages good architecture.
  6. multi-platform Moving between platforms is relatively seamless, since ReactiveX provides

    a standard implemented in many languages
 
 Learn once, use anywhere RxSwift RxJava RxKotlin Rx.NET RxJS RxDart RxGo and many more …
  7. Companies using Rx Rx is mature and has been in

    use by some of the biggest tech companies
  8. REACTIVE let canMakePurchase = Observable .combineLatest(userBalance, productPrice) .map { $0

    >= $1 } // Observable<Bool> canMakePurchase ALWAYS reflects the latest state canMakePurchase doesn’t reflect the latest state var userBalance = 5 let productPrice = 10 let canMakePurchase = userBalance >= productPrice print(canMakePurchase) userBalance += 20 print(canMakePurchase) // false // STILL false!
  9. Observable Int Independent Values 1 52 1337 52 70000 -24

    404 Observable<Int> Values over time 40 1 404 -24 1337
  10. observable lifecycle An Observable (aka Stream) may emit any number

    of next(value) events next next next next next next next next error X next next next completed X next When it emits a single error or completed event, the sequence will terminate
  11. everything is a stream Any values/events over time can be

    modeled as an Observable Stream tap Taps on a button: tap tap tap tap tap tap tap Networking: [Result] completed X Number of products in a shopping cart: 1 2 5 4 5 0
  12. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  13. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  14. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  15. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  16. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  17. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  18. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  19. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) Got next: 1 Got next: 4 Got next: -24 Got next: 400 subscribing Console output: Completed Disposed
  20. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) subscribing
  21. let numbers = Observable.of(1, 4, -24, 400) numbers.subscribe( onNext: {

    value in print("Got next: \(value)") }, onError: { error in print("An error occurred: \(error)") }, onCompleted: { print("Completed") }, onDisposed: { print("Disposed") }) .disposed(by: disposeBag) DisposeBag class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() ... ... } }
  22. DisposeBag Obs erver 4 Obs erver 3 Observ er 1

    Observ er 2 observable .subscribe(onNext: { item in // handle item }) .disposed(by: disposeBag) DisposeBag ViewController / Owner ViewController / Owner Deinit DisposeBag Deinit Observables in Bag Terminate & Dispose Created Subscription (In-Memory)
  23. Subject Observables are read only, but sometimes you need a

    type that you can write events into, or bind events into. That type is called an Observer. RxSwift provides an abstraction called Subject that is both an Observer and an Observable. Meaning, you can write events into the stream, and also subscribe to read events from it) Rx provides 4 types of Subjects: PublishSubject ReplaySubject BehaviorSubject AsyncSubject
  24. Subject PublishSubject ReplaySubject BehaviorSubject Doesn’t replay elements to new subscribers

    Replays latest element to new subscribers Replays any number of element to new subscribers A B C D Subscription starts here D PublishSubject<String>() D C BehaviorSubject<String>(value: "A") B C D ReplaySubject<String>.create(bufferSize: 2) let subject = ??? subject.onNext("A") subject.onNext("B") subject.onNext("C") subject.subscribe( ...) subject.onNext("D")
  25. Subject vs. Observable Subject Write Read Observable Read next next

    error next completed Events For the sake of simplicity you can think of an Observable as sort of an Output, while Subjects act more like Inputs.
  26. Declaring Behaviors One of the most powerful aspects of Rx

    is composing, manipulating and transforming Observables into other Observables in a way that defines Behavior (e.g. Declarative) Button tap Network Request Transformed to … Transformed to… [Model] Driving … UITableView [Person] All People [Person] Favorite people Filtered into … Driving … UITableView username String password String isValid Bool Button isEnabled Button Title Composed into … Button Title Driving … Driving … Transformed to
  27. Declaring Behaviors One of the most powerful aspects of Rx

    is composing, manipulating and transforming Observables into other Observables in a way that defines Behavior (e.g. Declarative) Button tap Network Request Transformed to … Transformed to… [Model] Driving … UITableView [Person] All People [Peron] Favorite people Filtered into … Driving … UITableView username String password String isValid Bool Button isEnabled Button Title Composed into … Button Title Driving … Driving … Transformed to
  28. Operators • Lets you manipulate the Observable stream • Most

    operators act the same between platforms, but some languages have behavior specific to them • Adds a "Mathematical" aspect to Rx • Most of them are Chainable (operator works on an Observable and returns an Observable) • Many “extra” operators exist in the community • You can quite easily make your own
  29. Operators ⌨ Throttle text input + Make sure we avoid

    duplicates Get JSON from Server ↩ Retry the request if it fails ⚔ If there are previous requests, cancel them Map to a Person Model Make sure we clear up memory when
 leaving the screen Our acceptance criteria: Make sure we can actually understand this
 code later
  30. Operators ⌨ Throttle text input + Make sure we avoid

    duplicates Get JSON from Server ↩ Retry the request if it fails ⚔ If there are previous requests, cancel them Map to a Person Model Make sure we clear up memory when
 leaving the screen Our acceptance criteria: Make sure we can actually understand this
 code later UITableView Delegate UITableView Data Source UISearchController Delegate UITextField Delegate URLSession Handler Custom throttling logic Custom duplicate logic HTTP request cancellation mechanism Custom retry logic
  31. Operators ⌨ Throttle text input + Make sure we avoid

    duplicates Get JSON from Server ↩ Retry the request if it fails ⚔ If there are previous requests, cancel them Map to a Person Model Make sure we clear up memory when
 leaving the screen Our acceptance criteria: Make sure we can actually understand this
 code later UITableView Delegate UITableView Data Source UISearchController Delegate UITextField Delegate URLSession Handler Custom throttling logic Custom duplicate logic HTTP request cancellation mechanism Custom retry logic Painful, hard to maintain piece of code
  32. Operators So, let’s say we do it in … 10

    Lines of Code. We want to make things more readable and easier to understand! Shit just got real.
  33. Operators searchBar.rx.text.orEmpty .throttle(0.3, scheduler: MainScheduler.instance) .distinctUntilChanged() // Observable<String> .flatMapLatest {

    API.getResults($0) .retry(3) } // Observable<[[String: Any]]> .map { Person.fromJSONArray($0) } // Observable<[Person]> .observeOn(MainScheduler.instance) .bind(to: tableView.rx.items(cellIdentifier: “Cell")) { row, person, cell in cell.textLabel ?.text = "\(person.name) @ row \(row)" } .disposed(by: disposeBag)
  34. Operators searchBar.rx.text.orEmpty .throttle(0.3, scheduler: MainScheduler.instance) .distinctUntilChanged() // Observable<String> .flatMapLatest {

    API.getResults($0) .retry(3) } // Observable<[[String: Any]]> .map { Person.fromJSONArray($0) } // Observable<[Person]> .observeOn(MainScheduler.instance) .bind(to: tableView.rx.items(cellIdentifier: “Cell")) { row, person, cell in cell.textLabel ?.text = "\(person.name) @ row \(row)" } .disposed(by: disposeBag)
  35. Operators searchBar.rx.text.orEmpty .throttle(0.3, scheduler: MainScheduler.instance) .distinctUntilChanged() // Observable<String> .flatMapLatest {

    API.getResults($0) .retry(3) } // Observable<[[String: Any]]> .map { Person.fromJSONArray($0) } // Observable<[Person]> .observeOn(MainScheduler.instance) .bind(to: tableView.rx.items(cellIdentifier: “Cell")) { row, person, cell in cell.textLabel ?.text = "\(person.name) @ row \(row)" } .disposed(by: disposeBag) These are all Operators !
  36. Error Handling Operators Transform Filter Create Combine Utilities A full

    list of operators is available at http://reactivex.io/documentation/operators.html create just of from deferred map flatMap flatMapLatest scan filter throttle debounce distinctUntilChanged skip take first last observeOn subscribeOn delay do materialize dematerialize retry retryWhen catchError catchErrorJustReturn combineLatest merge concat startWith zip
  37. ??? RxCocoa RxSwift Neutral implementation Based on ReactiveX iOS (UIKit)

    macOS (AppKit) watchOS (WatchKit) tvOS (UIKit)
  38. ??? RxCocoa Platform-specific Rx additions RxCocoa The glue that connects

    Observable streams to iOS, macOS, watchOS and tvOS UI Components RxSwift Neutral implementation Based on ReactiveX iOS (UIKit) macOS (AppKit) watchOS (WatchKit) tvOS (UIKit)
  39. RxCocoa & UI RxCocoa provides two Traits that represent properties

    and events on UI elements, and an Observer named Binder that lets you bind streams to UI elements ControlEvent ControlProperty *** Describes properties of UI Elements, e.g. UITextField -> text UISlider -> value
 UISwitch -> isOn etc… Describes events on UI Elements, e.g. tap changed didScroll etc… Describes bindings to UI Elements, e.g. alpha isEnabled isHidden etc… Binder
  40. RxSwift & UI ControlEvent ControlProperty *** RxCocoa covers most of

    the common UI elements in UIKit, AppKit, etc. Simply add .rx after your UI element to start discovering Binder button.rx.isEnabled button.rx.title(for: .normal) view.rx.isHidden view.rx.alpha textField.rx.text slider.rx.value datePicker.rx.date scrollView.rx.contentOffset switch.rx.isOn button.rx.tap collection.rx.itemSelected textView.rx.didBeginEditing gestureRec.rx.event
  41. Connecting the pieces Text field is empty Button is disabled

    Text field filled Button is enabled Button pressed - label changes based on Text Field
  42. DEFINING BEHAVIORS Text field is empty Button is disabled Text

    field filled Button is enabled Button pressed - label changes based on Text Field
  43. DEFINING BEHAVIORS txtName.rx.text.orEmpty // Observable<String> .map { !$0.isEmpty } //

    Observable<Bool> btnSend.rx.tap .withLatestFrom(txtName.rx.text.orEmpty) .map { "Hey there, \($0)!” } // Observable<String> .subscribe(onNext: { [unowned self] isEnabled in self.btnSend.isEnabled = isEnabled }) .disposed(by: disposeBag) .subscribe(onNext: { [unowned self] text in self.lblText.text = text }) .disposed(by: disposeBag)
  44. btnSend.rx.tap .withLatestFrom(txtName.rx.text.orEmpty) .map { "Hey there, \($0)!” } // Observable<String>

    DEFINING BEHAVIORS txtName.rx.text.orEmpty // Observable<String> .map { !$0.isEmpty } // Observable<Bool> .bind(to: btnSend.rx.isEnabled) .disposed(by: disposeBag) .bind(to: lblText.rx.text) .disposed(by: disposeBag)
  45. DEFINING BEHAVIORS txtName.rx.text.orEmpty .map { $0.isEmpty ? "" : "Hey

    there, \($0)!" } .bind(to: lblText.rx.text) .disposed(by: disposeBag)
  46. Extra Cocoa! ☕ RxCocoa provides many more abilities out of

    the scope of this lecture, for example: Driver Custom Binder Relay Signal
  47. It’s insanely hard! Use the .debug() operator to make your

    life easier Learning Curve is High, but once you “Think Rx”, it’s hard to go back :-) Use a DisposeBag and make
 sure everything is correctly
 disposed Ob ser ver Ob ser ver Obser Obser ver 2 DisposeBag
  48. 3rd party Libraries RxSwift’s Community is thriving with amazing Open-Source

    projects, be it wrappers around libraries you know, or entirely new Libraries. To find an Rx wrapper for a library or component you use, simply add Rx before, and you’ll probably find it available online ;-) RxSwiftCommunity @ GitHub RxAlamofire RxRealm RxGoogleMaps RxMoya RxStarscream RxMKMapView Other recommended libraries: RxSwiftExt RxDataSources Action RxAnimated RxFeedback ReactorKit And many many more...
  49. Got stuck? Have QUESTIONS? The best place to ask questions

    is in our awesome & welcoming Slack community. Feel free to join us ! + = http://slack.rxswift.org
  50. Want to learn more? Official RxSwift Documentation: http://github.com/ReactiveX/RxSwift Adam Borek’s

    blog: http://adamborek.com Marin Todorov’s Blog: http://rx-marin.com RxSwift: Reactive Programming with Swift (Book) https://store.raywenderlich.com/products/rxswift ReactiveX Documentation: http://reactivex.io/documentation/observable.html RxMarbles iOS App https://appstore.com/rxmarbles