RxSwift: an elegant weapon for a statically typed age

RxSwift: an elegant weapon for a statically typed age

Reactive Extensions are hardly a new concept, but it took some time to enter mainstream iOS development. For all of you wondering "is it worth the hype", here comes the RxSwift primer. We'll learn what the Observable is all about and how to use it without shooting ourselves in the foot. Why would you want to incorporate RxSwift into your project? Come and see!

43d2bef703ec7165166f161f137ac54f?s=128

Krzysztof Siejkowski

April 11, 2016
Tweet

Transcript

  1. 5.

    DATA ➡ NEXT[VALUE]* (COMPLETED | ERROR) Next("3"), Next("2"), Next("1"), Next("!"),

    Completed Next("The Spanish Inquisition"), Error(NobodyExpects) Error(RequestTimeout) Completed
  2. 6.

    public enum Event<Element> { case Next(Element) case Error(ErrorType) case Completed

    } protocol ObserverType { associatedtype Element } protocol ObservableType { associatedtype Element }
  3. 7.

    "a combination of the best ideas from the Observer pattern,

    the Iterator pattern, and functional programming" — reactivex.io
  4. 8.

    FROM OBSERVER PATTERN let notificationCenter : NSNotificationCenter notificationCenter.addObserverForName("ButtonTapped", object: nil,

    queue: nil) { (notification: NSNotification) in // observer closure } let observable: Observable<ButtonTappedEvent> observable.subscribeNext { (event: ButtonTappedEvent) in // observer closure }
  5. 9.

    FROM ITERATOR PATTERN let generator = [1, 2, 3, 4,

    5, 6].generate() while let elem = generator.next() { // elem is pulled from sequence } let observer = AnyObserver { event in // event is pushed by observable } [1, 2, 3, 4, 5, 6].asObservable().subscribe(observer)
  6. 10.

    FROM FUNCTIONAL PROGRAMMING [1, 2, 3, 4, 5, 6].filter {

    ... } .map { ... } .flatMap { ... } [1, 2, 3, 4, 5, 6].asObservable() .filter { ... } .map { ... } .flatMap { ... }
  7. 15.
  8. 18.

    CREATE WITH HELPER FUNCTIONS Observable<Int>.interval(1, scheduler: MainScheduler.instance) Observable.repeatElement("Did you miss

    me?") Observable.error(CouldHaveGoneBetter()) let v = Variable("Riding to Valhalla") v.asObservable() v.value = "Witness me!"
  9. 19.

    CREATE WITH CUSTOM LOGIC Observable<String>.create { observer in observer.onNext("keep calm")

    observer.onNext("and") observer.onNext("stay observable") observer.onCompleted() return NopDisposable.instance } ONLY ON VERY SPECIAL OCCASIONS
  10. 20.

    COMBINE: RXMARBLES.COM observable.concat(secondObservable) Observable.combineLatest(observable1, observable2, observable3) { elem1, elem2, elem3

    in elem1 + elem2 + elem3 } Observable.zip(observable1, observable2) { elem1, elem2 in (elem1, elem2) }
  11. 21.

    PERFORM WORK IN DATA PIPES observable .distinctUntilChanged() .filter { elem

    in elem > 0 } .map { [weak self] elem in self?.performWork(elem) } .flatMap { [weak self] elem in self?.useForAsyncOperation(elem) }
  12. 22.

    CONNECT EVENT STREAM TO YOUR LOGIC observable.subscribeNext { [weak self]

    elem in self?.processValue(elem) } observable.asDriver(onErrorJustReturn: "sorry!") .drive(label.rx_text)
  13. 23.

    RECAP > observable ➡ events ➡ observer > observer ❤

    iterator ❤ functional programming > customizable building block " > operators #
  14. 27.

    USER-GENERATED EVENTS let disposeBag = DisposeBag() let gestureRecognizer = UITapGestureRecognizer()

    gestureRecognizer.rx_event .subscribeNext { [weak self] _ in self?.processDetectedTap() } .addDisposableTo(disposeBag) view.addGestureRecognizer(gestureRecognizer)
  15. 28.

    CLEAN UP! let disposeBag = DisposeBag() let subscription = observable.subscribe(observer)

    subscription.addDisposableTo(disposeBag) ... disposeBag = nil
  16. 29.

    WEAKIFY SELF IN CLOSURES class SomeClass { func someMethod(param: Int)

    { ... } func otherMethod -> Observable<Void> { return observable.just(1) .map(someMethod) } } return observable.just(1).map { [weak self] elem in self?.someMethod(elem) ?? 42 }
  17. 31.

    NETWORK CALL class NetworkClient { func fetchSomeNews() -> Observable<News> {

    return NSURLSession.sharedSession() .rx_JSON("https://my.server.com/api/v2/news") .observeOn(SerializationScheduler.instance) .map { [weak self] json in self?.newsDeserializer.deserialize(json) } } }
  18. 35.

    1. Declaration thread until told otherwise. 2. Generation thread until

    changed. 3. Only first generation thread change counts. 4. No limit on work thread changes.
  19. 36.
  20. 37.

    MODEL-UI BINDING class MyViewController { func bindUserData() { let observable

    = self.userService.provideUserName() .observeOn(MainScheduler.instance) .shareReplay(1) observable.bindTo(self.userDataLabel.rx_text) .addDisposableTo(self.disposeBag) observable.subscribeNext { [weak self] userName in self?.navigationItem.title = userName }.addDisposableTo(self.disposeBag) } }
  21. 38.

    OBSERVABLE DOESN'T REMEMBER EVENTS let observable = Observable.just(1) .map {

    _ in print("I'm in map!") } observable.subscribeNext { _ in print("first") } observable.subscribeNext { _ in print("second") } // I'm in map! // first // I'm in map // second
  22. 39.

    UNTIL EXPLICITLY TOLD SO let observable = Observable.just(1) .map {

    _ in print("I'm in map!") } .shareReplay(1) observable.subscribeNext { _ in print("first") } observable.subscribeNext { _ in print("second") } // I'm in map! // first // second
  23. 41.

    almost every single developer that I talked to uses strong

    and inflammatory language about its unreadability. — Brent Simmons, Reactive Followup
  24. 42.

    EXTRACT LOGIC NSURLSession.sharedSession().rx_response(myNSURLRequest) .flatMap { (data: NSData!, response: NSURLResponse!) ->

    Observable<String> in guard let response = response as? NSHTTPURLResponse else { return Observable.error(yourNSError) } if 200 ..< 300 ~= response.statusCode { return just(transform(data)) } else { return Observable.error(yourNSError) } } NSURLSession.sharedSession().rx_response(myNSURLRequest) .flatMap { [weak self] data, response in self?.processResponse(data, response) }
  25. 43.

    USE PROVIDED HELPERS AND CREATE YOUR OWN observable.just("they see me

    rollin'") .catchError { _ in Observable.empty() } .observeOn(MainScheduler.instance) .shareReplayLatestWhileConnected() observable.just("they hatin'") .asDriver { _ in Driver.empty() }
  26. 44.

    RECAP > manage memory ! > identify the threads "

    > know when to share work # > good practices apply! $
  27. 46.

    OBSERVABLES SCALE > for networking? > for asynchronous operations? >

    for updating model and driving ui? > for communication between layers? > for all logic?
  28. 47.

    OBSERVABLES EXPRESS DATA FLOW fetchDataFromServer() .observeOn(BackgroundScheduler.instance) .map { deserializeFromJson() }

    .flatMap { decideOnCaching() } .map { performComputation() } .flatMap { updateModel() } .asDriver { _ in provideDefaultValue() } .driveNext { updateUI() } Advanced iOS Application Architecture and Patterns
  29. 48.

    OBSERVABLES REPLACE func optionalToObservable<T>(optional: T?) -> Observable<T> { return Observable.just(optional).flatMap

    { elem -> Observable<T> in if let optional = optional { return Observable.just(optional) } else { return Observable.error(NilError()) } } } optional, result, future, throws, sequencetype, ... KVO, NSNotificationCenter, target-action, callbacks, GCD, ...
  30. 49.

    OBSERVABLES UNIFORM func fun(observable: Observable<Int>) -> Observable<String> init<T, E :

    ObserverType where E.E = T>( dataObservable: Observable<T>, dataObserver: E ) { ... }
  31. 50.
  32. 51.

    LEARN ONCE, WRITE EVERYWHERE > ReactiveCocoa / Interstellar > RxJava

    / RxKotlin / RxGroovy > RxScala / RxClojure > RxPy / Rx.rb / RxJRuby > RxCpp / Rx.NET / UniRx > RxJS / Elm
  33. 52.

    DEVELOPER FRIENDLY guides at guides.rxswift.org docs at reactivex.io source at

    github.com/ReactiveX/RxSwift extensions at community.rxswift.org community at slack.rxswift.org
  34. 53.

    RECAP > use a little or a lot ! >

    uniform various concepts " > express data flow # > check the docs $ > consult the community %