$30 off During Our Annual Pro Sale. View Details »

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!

Krzysztof Siejkowski

April 11, 2016
Tweet

More Decks by Krzysztof Siejkowski

Other Decks in Programming

Transcript

  1. RXSWIFT
    AN ELEGANT WEAPON
    FOR A STATICALLY-TYPED AGE
    BY KRZYSZTOF SIEJKOWSKI

    View Slide


  2. WHAT'S RXSWIFT?

    View Slide

  3. REACTIVE EXTENSIONS

    View Slide

  4. OBSERVABLE GIVES DATA
    OBSERVER TAKES DATA

    View Slide

  5. DATA ➡ NEXT[VALUE]* (COMPLETED | ERROR)
    Next("3"), Next("2"), Next("1"), Next("!"), Completed
    Next("The Spanish Inquisition"), Error(NobodyExpects)
    Error(RequestTimeout)
    Completed

    View Slide

  6. public enum Event {
    case Next(Element)
    case Error(ErrorType)
    case Completed
    }
    protocol ObserverType {
    associatedtype Element
    }
    protocol ObservableType {
    associatedtype Element
    }

    View Slide

  7. "a combination of the best ideas
    from
    the Observer pattern,
    the Iterator pattern,
    and functional programming"
    — reactivex.io

    View Slide

  8. FROM OBSERVER PATTERN
    let notificationCenter : NSNotificationCenter
    notificationCenter.addObserverForName("ButtonTapped",
    object: nil, queue: nil) { (notification: NSNotification) in
    // observer closure
    }
    let observable: Observable
    observable.subscribeNext { (event: ButtonTappedEvent) in
    // observer closure
    }

    View Slide

  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)

    View Slide

  10. FROM FUNCTIONAL PROGRAMMING
    [1, 2, 3, 4, 5, 6].filter { ... }
    .map { ... }
    .flatMap { ... }
    [1, 2, 3, 4, 5, 6].asObservable()
    .filter { ... }
    .map { ... }
    .flatMap { ... }

    View Slide

  11. OBSERVABLE
    PUBLISHES AND
    TRANSFORMS EVENTS
    OBSERVER
    RECEIVES EVENTS

    View Slide

  12. THE REST IS
    UP TO YOU

    View Slide

  13. PROTOCOL
    UIVIEW
    OBSERVABLE

    View Slide


  14. HOW TO USE RXSWIFT?

    View Slide

  15. OPERATORS

    View Slide

  16. REACTIVEX.IO/DOCUMENTATION/
    OPERATORS.HTML
    GITHUB.COM/REACTIVEX/RXSWIFT/BLOB/
    MASTER/DOCUMENTATION/API.MD

    View Slide

  17. CREATE FROM EXISTING OBJECT
    ["Mobile", "Warsaw"].asObservable()
    textField.rx_text.asObservable()
    NSURLSession.sharedSession().rx_JSON(request)

    View Slide

  18. CREATE WITH HELPER FUNCTIONS
    Observable.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!"

    View Slide

  19. CREATE WITH CUSTOM LOGIC
    Observable.create { observer in
    observer.onNext("keep calm")
    observer.onNext("and")
    observer.onNext("stay observable")
    observer.onCompleted()
    return NopDisposable.instance
    }
    ONLY ON VERY SPECIAL OCCASIONS

    View Slide

  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)
    }

    View Slide

  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)
    }

    View Slide

  22. CONNECT EVENT STREAM TO YOUR LOGIC
    observable.subscribeNext { [weak self] elem in
    self?.processValue(elem)
    }
    observable.asDriver(onErrorJustReturn: "sorry!")
    .drive(label.rx_text)

    View Slide

  23. RECAP
    > observable ➡ events ➡ observer
    > observer ❤ iterator ❤ functional programming
    > customizable building block "
    > operators #

    View Slide

  24. !
    DON'T SHOOT YOURSELF
    IN THE FOOT

    View Slide

  25. Any sufficiently advanced technology is
    indistinguishable from magic.
    — Arthur C. Clarke
    BUT IT'S NOT MAGIC!

    View Slide

  26. !
    MEMORY
    MANAGEMENT

    View Slide

  27. USER-GENERATED EVENTS
    let disposeBag = DisposeBag()
    let gestureRecognizer = UITapGestureRecognizer()
    gestureRecognizer.rx_event
    .subscribeNext { [weak self] _ in
    self?.processDetectedTap()
    }
    .addDisposableTo(disposeBag)
    view.addGestureRecognizer(gestureRecognizer)

    View Slide

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

    View Slide

  29. WEAKIFY SELF IN CLOSURES
    class SomeClass {
    func someMethod(param: Int) { ... }
    func otherMethod -> Observable {
    return observable.just(1)
    .map(someMethod)
    }
    }
    return observable.just(1).map { [weak self] elem in
    self?.someMethod(elem) ?? 42
    }

    View Slide

  30. !
    ASYNCHRONOUS
    EXECUTION

    View Slide

  31. NETWORK CALL
    class NetworkClient {
    func fetchSomeNews() -> Observable {
    return NSURLSession.sharedSession()
    .rx_JSON("https://my.server.com/api/v2/news")
    .observeOn(SerializationScheduler.instance)
    .map { [weak self] json in
    self?.newsDeserializer.deserialize(json)
    }
    }
    }

    View Slide

  32. NETWORK CALL
    self.networkClient.fetchSomeNews()
    .observeOn(MainScheduler.instance)
    .subscribeNext { [weak self] news in
    self?.newsView.presentNews(news)
    }
    .addDisposableTo(disposeBag)

    View Slide

  33. OBSERVABLE
    IS SINGLE-THREADED

    View Slide

  34. DECLARATION THREAD
    subscribe
    GENERATION THREAD
    subscribeOn & CREATE METHODS
    WORK THREAD(S)
    observeOn

    View Slide

  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.

    View Slide

  36. !
    STATE

    View Slide

  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)
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  40. !
    READABILITY

    View Slide

  41. almost every single developer
    that I talked to uses strong and
    inflammatory language about its
    unreadability.
    — Brent Simmons, Reactive Followup

    View Slide

  42. EXTRACT LOGIC
    NSURLSession.sharedSession().rx_response(myNSURLRequest)
    .flatMap { (data: NSData!, response: NSURLResponse!) -> Observable 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) }

    View Slide

  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() }

    View Slide

  44. RECAP
    > manage memory !
    > identify the threads "
    > know when to share work #
    > good practices apply! $

    View Slide

  45. !
    ALL THINGS OBSERVABLE

    View Slide

  46. OBSERVABLES SCALE
    > for networking?
    > for asynchronous operations?
    > for updating model and driving ui?
    > for communication between layers?
    > for all logic?

    View Slide

  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

    View Slide

  48. OBSERVABLES REPLACE
    func optionalToObservable(optional: T?) -> Observable {
    return Observable.just(optional).flatMap { elem -> Observable 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, ...

    View Slide

  49. OBSERVABLES UNIFORM
    func fun(observable: Observable) -> Observable
    init(
    dataObservable: Observable,
    dataObserver: E
    ) { ... }

    View Slide

  50. SWIFTY id

    View Slide

  51. LEARN ONCE, WRITE EVERYWHERE
    > ReactiveCocoa / Interstellar
    > RxJava / RxKotlin / RxGroovy
    > RxScala / RxClojure
    > RxPy / Rx.rb / RxJRuby
    > RxCpp / Rx.NET / UniRx
    > RxJS / Elm

    View Slide

  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

    View Slide

  53. RECAP
    > use a little or a lot !
    > uniform various concepts "
    > express data flow #
    > check the docs $
    > consult the community %

    View Slide

  54. THANK YOU!
    QUESTIONS?

    View Slide