Save 37% off PRO during our Black Friday Sale! »

Be Proactive, Use Reactive!

Be Proactive, Use Reactive!

A general overview of reactive programming as presented at CodeMash 2014, with examples in Swift and ReactiveCocoa.

F3c52a0537360c85b46e043711cab0c6?s=128

Andrew Sardone

January 08, 2015
Tweet

Transcript

  1. BE PROACTIVE USE REACTIVE

  2. Nutshell.com CodeOptional.com @andrewa2 github.com/andrewsardone (by Kevin Vitale) andrew sardone

  3. What is reactive programming?

  4. “ ” reactive |rēˈaktiv| … a programming paradigm oriented around

    data flows and the propagation of change
  5. None
  6. None
  7. reactive programming data flows propagation of change =

  8. None
  9. we all respond to flowing data and react to changes

    Model View Controller
  10. Cocoa’s KVO? NSNotificationCenter? Ember or Angular observers? Callbacks? Listeners? How

    is this different from… Programming?
  11. Back to the basics

  12. Subject Observer notify()

  13. DATA STREAMS

  14. Data type a value over time

  15. •updated property values •mouse cursor position •typing characters into a

    text field •database records
  16. Collection of emitted events ordered in time

  17. with functions for observation

  18. onNext( ) onComplete()

  19. onNext( ) onNext( ) onError() x

  20. [ "value”, "other value”, "another value” ] "a value", "other

    value", "another value"
  21. textField.rac_textSignal() .subscribeNext({ (_) -> Void in // value is emitted

    }, error: { (error) -> Void in // an error occurs within the stream }, completed: { () -> Void in // signal completes successfully })
  22. Stream.transform(f: (x) -> y) -> Stream Streams are composable

  23. 1 3 2 2 6 4 map { (x) ->

    Int in x * 2 } Streams are composable
  24. 1 2 4 filter { (x) -> Bool in x

    % 2 == 0 } 4 2 Streams are composable
  25. Double tap! Demo

  26. None
  27. taps .bufferWithTime(0.5) .map { ($0 as RACTuple).count } .filter {

    ($0 as Int) == 2 } .subscribeNext { (_) in println("Double tap!") }
  28. taps .bufferWithTime(0.5) .map { ($0 as RACTuple).count } .filter {

    ($0 as Int) == 2 } .subscribeNext { (_) in println("Double tap!") }
  29. taps .bufferWithTime(0.5) .map { ($0 as RACTuple).count } .filter {

    ($0 as Int) == 2 } .subscribeNext { (_) in println("Double tap!") }
  30. 1 2 3 taps .bufferWithTime(0.5) .map { ($0 as RACTuple).count

    } .filter { ($0 as Int) == 2 } .subscribeNext { (_) in println("Double tap!") }
  31. 2 taps .bufferWithTime(0.5) .map { ($0 as RACTuple).count } .filter

    { ($0 as Int) == 2 } .subscribeNext { (_) in println("Double tap!") }
  32. taps .bufferWithTime(0.5) .map { ($0 as RACTuple).count } .filter {

    ($0 as Int) == 2 } .subscribeNext { (_) in println("Double tap!") } Double tap!
  33. RACSignal.combineLatest([ slowToEmitStream, evenSlowerToEmitStream, ]) .doNext { let tuple = $0

    as RACTuple processResults( tuple.first, tuple.second) } .subscribeCompleted { () -> Void in println("The work is done!") }
  34. MODEL WITH STREAMS

  35. MODEL WITH STREAMS BUT…WHY?

  36. Claim code should communicate intent

  37. Current tooling is insufficient

  38. map extension Array { func map<U>(transform: (T) -> U) ->

    [U] }
  39. let input = [1, 2, 3] var output = [Int]()

    for number in input { output.append(number * 2) } // output: [2, 4, 6] let output = [1, 2, 3].map { $0 * 2 } // output: [2, 4, 6]
  40. let input = [1, 2, 3] var output = [Int]()

    for number in input { output.append(number * 2) } // output: [2, 4, 6] let output = [1, 2, 3].map { $0 * 2 } // output: [2, 4, 6] Concept: 1-1 mapping
  41. map reifies 1-1 data transformation

  42. Good Thing™ reify |ˈrēəәˌfī| make (something abstract) more concrete or

    real
  43. reactive programming reifies event-driven software

  44. None
  45. var count = 0 upButton.addTarget(self, action: “upButtonTouched:”, forControlEvents: .TouchUpInside) downButton.addTarget(self,

    action: “downButtonTouched:", forControlEvents: .TouchUpInside) countLabel.text = String(count) func upButtonTouched(sender: UIButton) { count++ countLabel.text = String(count) } func downButtonTouched(sender: UIButton) { count-- countLabel.text = String(count) }
  46. let upTaps = upButton .rac_signalForControlEvents(.TouchUpInside) let downTaps = downButton .rac_signalForControlEvents(.TouchUpInside)

    let count = RACSignal.merge([ RACSignal.return(0), upTaps.mapReplace(1), downTaps.mapReplace(-1) ]) .scanWithStart(0, reduce: { $0 + $1 }) .map { String($0) } count ~> RAC(self, "countLabel.text")
  47. client.loginWithSuccess({ client.loadCachedTweetsWithSuccess({ (tweets) in client.fetchTweetsAfterTweet(tweets.last, success: { (tweets) -> Void

    in // Now we can show our tweets }, failure: { (error) -> Void in presentError(error) }) }, failure: { (error) -> Void in presentError(error) }) }, failure: { (error) -> Void in presentError(error) })
  48. client.login() .then { return client.loadCachedTweets() } .flattenMap { (tweets) ->

    RACStream! in return client.fetchTweetsAfterTweet(tweets.last) } .subscribeError({ (error) -> Void in presentError(error) }, completed: { () -> Void in // Now we can show our tweets })
  49. func loginWithSuccess( success: () -> Void, failure: (NSError) -> Void)

    { // function doesn’t return anything // it jumps into two callbacks based on return } func login() -> RACSignal { // function has a native return value // a data stream object that we can observe }
  50. SO… HOW DO I DO THIS?

  51. ReactiveCocoa ReactiveExtensions RxJava Bacon.js Elm github.com/ReactiveCocoa/ReactiveCocoa github.com/Reactive-Extensions github.com/ReactiveX/RxJava github.com/baconjs/bacon.js elm-lang.org

  52. SOURCES FURTHER READING

  53. Missing RX Intro Input & Output RxMarbles Other j.mp/missingrxintro j.mp/joshaberio

    rxmarbles.com pinboard.in/u:andrewsardone/t:reactive
  54. B Y E (photo by Chris Dzombak) (puppy by Shane

    Bliemaster)