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

Reactive Thinking in iOS Development

Reactive Thinking in iOS Development

In a world where Imperative Programming is the most used paradigm, Reactive comes up to make our code more reusable, robust, and stateless. Learn what Functional Reactive Programming means and how it could help you with problems you have to face daily in your projects. We’ll present basic concepts and practical examples for iOS developers that will help you to start thinking in streams, observers, .. and mix them with cool Swift functional concepts.

B0a336761194918a853deeff1f22b537?s=128

Pedro Piñera Buendía

May 11, 2016
Tweet

More Decks by Pedro Piñera Buendía

Other Decks in Technology

Transcript

  1. REACTIVE THINKING IN IOS DEVELOPMENT @PEPIBUMUR / @SAKY

  2. WHO? @pepibumur iOS Developer at SoundCloud GitHub: pepibumur Twitter: pepibumur

    @saky iOS Developer at Letgo GitHub: isaacroldan Twitter: saky GitDo.io our spare time project
  3. INDEX > Programming Paradigms > Reactive Libraries > Reactive Motivation

    > Reactive Thinking > Reactive Caveats > Conclusion
  4. PARADIGMS ! WAYS OF SEEING THE WORLD WHEN IT COMES

    TO PROGRAMMING
  5. WIKIPEDIA Data-Driven, Declarative, Dynamic, End-User, Event-Driven, Expression-Oriented, Feature-Oriented, Function-level, Generic,

    Imperative, Inductive, Language Oriented, Metaprogramming, Non-Structured, Nondeterministic, Parallel computing, Point-free Style, Structured, Value- Level, Probabilistic
  6. IMPERATIVE PROGRAMMING DECLARATIVE PROGRAMMING

  7. IMPERATIVE PROGRAMMING DECLARATIVE PROGRAMMING

  8. HOW SEQUENCE OF STEPS THAT HAPPEN IN ORDER

  9. NATURAL WAY TO PROGRAM

  10. EXECUTION STATE (AKA SIDE EFFECT)

  11. IMPERATIVE PROGRAMMING func userDidSearch(term: String) { let apiReults = api.search(term:

    term).execute() self.items = self.adaptResults(apiResults) self.tableView.reloadData() }
  12. IMPERATIVE PROGRAMMING DECLARATIVE PROGRAMMING

  13. WHAT IT DOESN'T DESCRIBE THE CONTROL FLOW

  14. HTML

  15. HTML SQL

  16. HTML SQL REACTIVE PROGRAMMING

  17. DECLARATIVE PROGRAMMING let predicate = NSPredicate(format: "name == %@", "Pedro")

    let regex = NSRegularExpression(pattern: ".+", options: 0)
  18. REACTIVE PROGRAMMING DATA-FLOW PROGRAMMING (DESCRIBES THE STATE PROPAGATION) # THE

    INTRODUCTION TO REACTIVE PROGRAMMING THAT YOU'VE BEEN MISSING
  19. FUNCTIONAL REACTIVE PROGRAMMING (AKA FRP)

  20. REACTIVE LIBRARIES > RxSwift (ReactiveX) > ReactiveCocoa > BrightFutures >

    ReactKit > Bond > More and more... PromiseKit, Bolts...
  21. WHAT LIBRARY SHOULD I USE? !

  22. WHAT LIBRARY SHOULD I USE? ! DO I NEED A

    LIBRARY FOR THIS? !
  23. SWIFT var userName: String { didSet { // React to

    changes in variables } }
  24. SWIFT var userName: String { didSet { // React to

    changes in variables view.updateTitle(userName) } }
  25. MOTIVATION ! WHY SHOULD I STICK TO REACTIVE PROGRAMMING?

  26. > Bindings > Composability > Threading

  27. > Bindings > Composability > Threading

  28. BINDING

  29. UI BINDING

  30. UI BINDING

  31. UI BINDING

  32. > Bindings > Composability > Threading

  33. None
  34. None
  35. > Bindings > Composability > Threading

  36. > Bindings > Composability > Threading (observer and execution)

  37. THREADING

  38. REACTIVE THINKING !

  39. THINKING IN TERMS OF OBSERVABLES OR SIGNALS/PRODUCERS IN REACTIVECOCOA

  40. ACTIONS CAN BE OBSERVED HOW? !

  41. .NEXT(T) .ERROR(ERRORTYPE) .COMPLETE

  42. OPERATIONS RxSwift Observable<String>.create { (observer) -> Disposable in observer.onNext("next value")

    observer.onCompleted() return NopDisposable.instance // For disposing the action } ReactiveCocoa SignalProducer<String>.create { (observer, disposable) in observer.sendNext("next value") observer.sendComplete() }
  43. EXISTING PATTERNS RXSWIFT ▶︎ RXCOCOA (EXTENSIONS) REACTIVECOCOA ▶︎ DO IT

    YOURSELF
  44. UIKIT let button = UIButton() button.rx_controlEvent(.TouchUpInside) .subscribeNext { _ in

    print("The button was tapped") }
  45. NOTIFICATIONS NSNotificationCenter.defaultCenter() .rx_notification("my_notification", object: nil) .subscribeNext { notification // We

    got a notification }
  46. DELEGATES self.tableView.rx_delegate // DelegateProxy .observe(#selector(UITableViewDelegate.tableView(_:didSelectRowAtIndexPath:))) .subscribeNext { (parameters) in //

    User did select cell at index path }
  47. PLAYING ! WITH OBSERVABLES

  48. OBSERVABLE let tracksFetcher = api.fetchTracks // Background .asObservable()

  49. ERROR HANDLING let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3)

    .catchErrorJustReturn([])
  50. MAPPING let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([])

    .map(TrackEntity.mapper().map)
  51. FILTERING let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([])

    .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) }
  52. FLATMAPPING let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([])

    .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) }
  53. OBSERVATION THREAD let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3)

    .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) } .observeOn(MainScheduler.instance) // Main thread
  54. LE IMPERATIVE WAY !" func fetchTracks(retry retry: Int = 0,

    retryLimit: Int, query: query, mapper: (AnyObject) -> Track, completion: ([UIImage], Error?) -> ()) { api.fetchTracksWithCompletion { (json, error) in if let _ = error where retry < retryLimit { fetchTracksWithRetry(retry: retry+1, retryLimit: retryLimit, query: query, mapper: mapper, completion: completion) } else if let error = error { completion([], error) } else if let json = json { guard let jsonArray = json as? [AnyObject] else { completion([], Error.InvalidResponse) return } let mappedTracks = jsonArray.map(mapper) let filteredTracks = mappedTracks.filter { $0.name.contains(query) } self.fetchImages(track: filteredTracks, completion: ([])) let trackImages = self.fetchImages(tracks: filteredTracks, completion: { (images, error) in dispatch_async(dispatch_get_main_queue(),{ if let error = error { completion([], error) return } completion(images, error) }) }) } } }
  55. THROTTLING CLASSIC REACTIVE EXAMPLE func tracksFetcher(query: String) -> Observable<[TrackEntity]> searchTextField

    .rx_text.throttle(0.5, scheduler: MainScheduler.instance) .flatmap(tracksFetcher) .subscribeNext { tracks in // Yai! Tracks searched }
  56. OTHER OPERATORS Combining / Skipping Values / Deferring / Concatenation

    / Take some values / Zipping
  57. OBSERVING ! EVENTS

  58. SUBSCRIBING observable subscribe { event switch (event) { case .Next(let

    value): print(value) case .Completed: print("completed") case .Error(let error): print("Error: \(error)") } }
  59. BIND CHANGES OVER THE TIME TO AN OBSERVABLE OBSERVABLE ▶

    BINDING ▶ OBSERVER
  60. BINDING To a Variable let myVariable: Variable<String> = Variable("") observable

    .bindTo(myVariable) print(myVariable.value) To UI observable .bindTo(label.rx_text)
  61. None
  62. ! CAVEATS BECAUSE YES... IT COULDN'T BE PERFECT

  63. DEBUGGING DEBUG OPERATOR IN RXSWIFT

  64. let tracksFetcher = api.fetchTracks // Background .asObservable() .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map)

    .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) } .observeOn(MainScheduler.instance) // Main thread
  65. let tracksFetcher = api.fetchTracks // Background .asObservable .debug("after_fetch") // <--

    Debugging probes .retry(3) .catchErrorJustReturn([]) .map(TrackEntity.mapper().map) .debug("mapped_results") // <-- Debugging probes .filter { $0.name.contains(query) } .flatMap { self.rx_trackImage(track: $0) } .observeOn(MainScheduler.instance) // Main thread
  66. //let tracksFetcher = api.fetchTracks // Background // .asObservable .debug("after_fetch") //

    <-- Debugging probes // .retry(3) // .catchErrorJustReturn([]) // .map(TrackEntity.mapper().map) .debug("mapped_results") // <-- Debugging probes // .filter { $0.name.contains(query) } // .flatMap { self.rx_trackImage(track: $0) } // .observeOn(MainScheduler.instance) // Main thread
  67. > [after_fetch] Next Event... // Downloaded tracks > [mapped_results] Error

    ... // Detected error after mapping > [...]
  68. RETAIN CYCLES class IssuePresenter { var disposable: Disposable func fetch()

    { self.disposable = issueTitle .observable() .bindTo { self.titleLabel.rx_text } } }
  69. UNSUBSCRIPTION YOU NEED TO TAKE CARE OF THE LIFECYCLE OF

    YOUR OBSERVABLES
  70. UNSUBSCRIPTION title.asObservable().bindTo(field.rx_text) // RxSwift will show a warning

  71. UNSUBSCRIPTION let titleDisposable = title.asObservable().bindTo(field.rx_text) titleDisposable.dispose()

  72. UNSUBSCRIPTION let disposeBag = DisposeBag() title.asObservable() .bindTo(field.rx_text) .addDisposableTo(disposeBag) // The

    binding is active until the disposeBag is deallocated
  73. CONCLUSIONS

  74. PREVENTS STATEFUL CODE

  75. DATA FLOW MANIPULATION BECOMES EASIER

  76. BUT... !

  77. YOU COUPLE YOUR PROJECT TO A LIBRARY !

  78. REACTIVE CODE SPREADS LIKE A VIRUS ! OVERREACTIVE ⚠

  79. DEFINE REACTIVE DESIGN GUIDELINES AND STICK TO THEM

  80. HAVE REACTIVE FUN !

  81. REFERENCES > rxmarbles.com > RxSwift Community > RxSwift Repository >

    ReactiveCocoa
  82. WE ARE HIRING PEPI@SOUNDCLOUD.COM - ISAAC@LETGO.COM ❄ BERLIN - BARCELONA

    !
  83. None
  84. THANKS QUESTIONS? SLIDES HTTP://BIT.LY/1RFWLCI @SAKY - @PEPIBUMUR