Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ReactiveCocoa workshop

141 views

Published on

Slides from a ReactiveCocoa workshop that took place at ProMobiConf (24.08.2016) in Warsaw.

Published in: Technology
  • Be the first to comment

ReactiveCocoa workshop

  1. 1. Building iOS apps with ReactiveCocoa
  2. 2. About me Eliasz Sawicki Blog: http://eluss.github.io/ Twitter: @EliSawic
  3. 3. Let's begin
  4. 4. ReactiveCocoa
  5. 5. Functional Reactive Programming
  6. 6. Wikipedia Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter).
  7. 7. Reactive Programming
  8. 8. Reactive Programming • Working with asynchronous dataflow • Reacting to state changes
  9. 9. Functional Programming
  10. 10. Functional Programming • Immutable
  11. 11. assert(f(x) == f(x))
  12. 12. A person class Person { var name: String init(name: String) { self.name = name } }
  13. 13. Mutable func personWithReversedName(person: Person) -> Person { person.name = String(person.name.characters.reverse()) return person } let person = Person(name: "John") let reversedA = personWithReversedName(person) print(reversedA.name) // nhoJ let reversedB = personWithReversedName(person) print(reversedB.name) // John
  14. 14. Immutable func personWithReversedName(person: Person) -> Person { let name = String(person.name.characters.reverse()) let newPerson = Person(name: name) return newPerson } let person = Person(name: "John") let reversedA = personWithReversedName(person) print(reversedA.name) // nhoJ let reversedB = personWithReversedName(person) print(reversedB.name) // nhoJ
  15. 15. Functional Programming • Immutable • Stateless
  16. 16. Stateful var value = 0 func increment() { value += 1 }
  17. 17. Stateless func increment(value: Int) -> Int { return value + 1 }
  18. 18. Declarative
  19. 19. Imperative vs Declarative
  20. 20. Imperative let array = [0, 1, 2, 3, 4, 5] var evenNumbers = [Int]() for element in array { if element % 2 == 0 { evenNumbers.append(element) } }
  21. 21. Declarative let array = [0, 1, 2, 3, 4, 5] let evenNumbers = array.filter { $0 % 2 == 0 }
  22. 22. Back to ReactiveCocoa
  23. 23. Event streams
  24. 24. Event Stream
  25. 25. Event
  26. 26. Non-Terminating • Next Terminating • Completed • Failed • Interrupted
  27. 27. Observer
  28. 28. Signal
  29. 29. What is it? • Represents events over time • Must be observed in order to access it's events • Observing a signal does not trigger any side effects (push based) • No random access to events
  30. 30. Signal's lifetime • Passes any number of Next events • "Dies" when terminating event arrives • Any new observer will receive Interrupted event
  31. 31. Observing signal.observe { (event) in print(event) } signal.observeNext { (value) in print(value) } signal.observeCompleter { print("Completed") }
  32. 32. Creating Signals
  33. 33. Basic signal Signal<String, NSError> { (observer) -> Disposable? in observer.sendNext("test") observer.sendCompleted() return ActionDisposable(action: { print("Signal disposed") }) }
  34. 34. Pipe
  35. 35. let (signal, observer) = Signal<String, NoError>.pipe() signal.observeNext({ text in print(text) }) signal.observeCompleted({ print("Test completed") }) observer.sendNext("It's a test") // It's a test observer.sendCompleted() // Test completed
  36. 36. SignalProducer
  37. 37. What is it? • Represents tasks • Creates signals • Performs side effects • Does not start it's work if not started
  38. 38. Injecting side effects let producer = signalProducer .on(started: { print("Started") }, event: { event in print("Event: (event)") }, failed: { error in print("Failed: (error)") }, completed: { print("Completed") }, interrupted: { print("Interrupted") }, terminated: { print("Terminated") }, disposed: { print("Disposed") }, next: { value in print("Next: (value)") })
  39. 39. Creating Signal Producers
  40. 40. Basic Signal Producer SignalProducer<String, NSError> { (observer, composite) in composite.addDisposable({ print("Clearing work") }) observer.sendNext("In Progres...") observer.sendCompleted() }
  41. 41. Buffer let (producer, observer) = SignalProducer<String, NoError>.buffer(3) observer.sendNext("test") observer.sendCompleted() producer.startWithSignal { (signal, disposable) in signal.observeNext({ (text) in print(text) // test }) signal.observeCompleted({ print("Test completed") // Test completed }) } observer.sendNext("is interrupted") observer.sendInterrupted() producer.startWithSignal { (signal, disposable) in signal.observeNext({ (text) in print(text) // test, is interrupted }) signal.observeInterrupted({ print("Test interrupted") // Test interrupted }) }
  42. 42. Manipulating signals
  43. 43. Map
  44. 44. Map let (numberSignal, observer) = Signal<Int, NoError>.pipe() let textSignal = numberSignal.map { (number) -> String in return "Number is (number)" } numberSignal.observeNext { (number) in print(number) // 5 } textSignal.observeNext { (text) in print(text) // Number is 5 } observer.sendNext(5)
  45. 45. Filter
  46. 46. Filter let (numberSignal, observer) = Signal<Int, NoError>.pipe() let fiveSignal = numberSignal.filter { (number) -> Bool in return number == 5 } numberSignal.observeNext { (number) in print(number) // 6, 5 } fiveSignal.observeNext { (number) in print(number) // 5 } observer.sendNext(6) observer.sendNext(5)
  47. 47. Aggregating
  48. 48. Aggregating let (numberSignal, observer) = Signal<Int, NoError>.pipe() let aggregtingSignal = numberSignal.reduce(0) { (currentValue, addedValue) -> Int in return currentValue + addedValue } numberSignal.observeNext { (number) in print(number) // 5, 6 } aggregtingSignal.observeNext { (number) in print("Aggregated (number)") // Aggregated 11 } observer.sendNext(5) observer.sendNext(6) observer.sendCompleted()
  49. 49. Skip repeats
  50. 50. Skip repeats let (numberSignal, observer) = Signal<Int, NoError>.pipe() numberSignal.observeNext { (number) in print(number) // 1, 2, 2, 3 } numberSignal.skipRepeats().observeNext { (number) in print(number) // 1, 2, 3 } observer.sendNext(1) observer.sendNext(2) observer.sendNext(2) observer.sendNext(3)
  51. 51. Skip until
  52. 52. Skip until let (numberSignal, observer) = Signal<Int, NoError>.pipe() numberSignal.observeNext { (number) in print(number) // 5, 6 } let (trigger, triggerObserver) = Signal<Void, NoError>.pipe() numberSignal.skipUntil(trigger).observeNext { (number) in print("Triggered (number)") // Triggered 6 } observer.sendNext(5) triggerObserver.sendNext() observer.sendNext(6)
  53. 53. Collect let (numberSignal, observer) = Signal<Int, NoError>.pipe() numberSignal.observeNext { (number) in print(number) // 1, 2, 3, 4, 5 } numberSignal.collect { (values) -> Bool in return values.reduce(0, combine: +) > 4 }.observeNext { (values) in print(values) // [1, 2, 3], [4 ,5] } observer.sendNext(1) observer.sendNext(2) observer.sendNext(3) observer.sendNext(4) observer.sendNext(5)
  54. 54. Manipulating multiple signals
  55. 55. Combine latest
  56. 56. Combine latest
  57. 57. let (numberSignal, numberObserver) = Signal<Int, NoError>.pipe() let (textSignal, textObserver) = Signal<String, NoError>.pipe() combineLatest(numberSignal, textSignal).observeNext { (number, text) in print("(text) - (number)") } numberObserver.sendNext(1) // Nothing printed textObserver.sendNext("John") // John - 1 numberObserver.sendNext(2) // John - 2 textObserver.sendNext("Mary") // Mary - 2
  58. 58. Zip
  59. 59. Zip let (menSignal, menObserver) = Signal<String, NoError>.pipe() let (womenSignal, womenObserver) = Signal<String, NoError>.pipe() let zippedSignal = zip(menSignal, womenSignal) zippedSignal.observeNext { (man, woman) in print("New couple - (man) and (woman)") } zippedSignal.observeCompleted({ print("Completed") }) menObserver.sendNext("John") // Nothing printed menObserver.sendNext("Tom") // Nothing printed womenObserver.sendNext("Lisa") // New couple - John and Lisa menObserver.sendNext("Greg") // Nothing printed menObserver.sendCompleted() womenObserver.sendNext("Sandra") // New couple - Tom and Sandra womenObserver.sendNext("Mary") // New couple - Greg and Mary, Completed
  60. 60. Merge
  61. 61. Merge let (menSignal, menObserver) = Signal<String, NoError>.pipe() let (womenSignal, womenObserver) = Signal<String, NoError>.pipe() let (peopleSignal, peopleObserver) = Signal<Signal<String, NoError>, NoError>.pipe() peopleSignal.flatten(.Merge).observeNext { (name) in print(name) } peopleObserver.sendNext(menSignal) peopleObserver.sendNext(womenSignal) menObserver.sendNext("John") // John womenObserver.sendNext("Lisa") // Lisa
  62. 62. Handling errors
  63. 63. Catching errors let (producer, observer) = SignalProducer<String, NSError>.buffer(5) let error = NSError(domain: "domain", code: 0, userInfo: nil) producer .flatMapError { _ in SignalProducer<String, NoError>(value: "Default") } .startWithNext { next in print(next) } observer.sendNext("First") // prints "First" observer.sendNext("Second") // prints "Second" observer.sendFailed(error) // prints "Default"
  64. 64. Retry var tries = 0 let limit = 2 let error = NSError(domain: "domain", code: 0, userInfo: nil) let producer = SignalProducer<String, NSError> { (observer, _) in if tries++ < limit { observer.sendFailed(error) } else { observer.sendNext("Success") observer.sendCompleted() } } producer .on(failed: {e in print("Failure")}).retry(2).start { event in // prints "Failure" twice switch event { case let .Next(next): print(next) // prints "Success" case let .Failed(error): print("Failed: (error)") } }
  65. 65. Promoting let (numbersSignal, numbersObserver) = Signal<Int, NoError>.pipe() let (lettersSignal, lettersObserver) = Signal<String, NSError>.pipe() numbersSignal .promoteErrors(NSError) .combineLatestWith(lettersSignal)
  66. 66. Mapping errors
  67. 67. Properties
  68. 68. Properties • AnyProperty • ConstantProperty • MutableProperty • DynamicProperty
  69. 69. MutableProperty let name = MutableProperty<String>("Bob") name.producer.startWithNext { (text) in print(text) } name.modify { (name) -> String in return name + "!" } name.value = "Lisa"
  70. 70. DynamicProperty let textProperty = DynamicProperty(object: textField, keyPath: "text") textProperty.producer.startWithNext { (text) in print(text) } textProperty.value = "Textfield text"
  71. 71. Bindings
  72. 72. Basic binding let property = MutableProperty<String>("") let (producer, _) = SignalProducer<String, NoError>.buffer(1) let (signal, _) = Signal<String, NoError>.pipe() property <~ producer property <~ signal
  73. 73. Action
  74. 74. Create Action let action = Action<Int, String, NSError>({ (number) -> SignalProducer<String, NSError> in return SignalProducer<String, NSError> {observer, disposable in observer.sendNext("Number is (number)") observer.sendCompleted() } })
  75. 75. Create signal producer let producer = action.apply(1)
  76. 76. Execute action prodcuer.startWithSignal { (signal, disposable ) in signal.observeNext({ (value) in print("(value)") }) signal.observeFailed({ (actionError) in print("(actionError)") }) }
  77. 77. Observing actions let action = Action<Int, String, NSError>({ (number) -> SignalProducer<String, NSError> in return SignalProducer<String, NSError> {observer, disposable in observer.sendNext("Number is (number)") observer.sendCompleted() } }) action.values.observe { (value) in print("Value: (value)") } action.errors.observe { (error) in print("Error: (error)") } action.events.observe { (event) in print("Event: (event)") } action.apply(5).startWithSignal { (_ , _ ) in }
  78. 78. CocoaAction
  79. 79. Prepare Action var text = MutableProperty<String>("Switch is on") let switchControl = UISwitch() let switchAction = Action<Bool, String, NoError>({ (isOn) -> SignalProducer<String, NoError> in return SignalProducer<String, NoError> { observer, disposable in observer.sendNext(isOn ? "Switch is on" : "Switch is off") observer.sendCompleted() } })
  80. 80. Create CocoaAction let switchCocoaAction = CocoaAction(switchAction, { (control) -> Bool in let control = control as! UISwitch return control.on }) switchControl.addTarget(switchCocoaAction, action: CocoaAction.selector, forControlEvents: .ValueChanged) text <~ switchAction.values
  81. 81. Schedulers • SchedulerType • ImmediateScheduler • UIScheduler • DateSchedulerType • QueueScheduler • TestScheduler
  82. 82. Memory Management
  83. 83. Disposables
  84. 84. Task let producer = SignalProducer<String, NoError> { (observer, composite) in let date = NSDate().dateByAddingTimeInterval(10) composite += QueueScheduler().scheduleAfter(date, action: { print("Doing my work") // Doing my work observer.sendNext("Test") observer.sendCompleted() }) } producer.startWithSignal { (signal, disposable) in signal.observeNext({ (value) in print(value) // Test }) signal.observeCompleted({ print("Work completed") // Work completed }) }
  85. 85. Cancelling work let producer = SignalProducer<String, NoError> { (observer, composite) in let date = NSDate().dateByAddingTimeInterval(10) composite += QueueScheduler().scheduleAfter(date, action: { print("Doing my work") // Not printed observer.sendNext("Test") observer.sendCompleted() }) } producer.startWithSignal { (signal, disposable) in signal.observeNext({ (value) in print(value) // Not printed }) signal.observeInterrupted({ print("Work interrupted") // Work interrupted }) let date = NSDate().dateByAddingTimeInterval(2) QueueScheduler().scheduleAfter(date, action: { disposable.dispose() }) }
  86. 86. Cleaning let producer = SignalProducer<String, NoError> { (observer, composite) in composite.addDisposable({ print("I'm done") }) let date = NSDate().dateByAddingTimeInterval(4) composite += QueueScheduler().scheduleAfter(date, action: { print("Doing my work") // Not printed }) } producer.startWithSignal { (signal, disposable) in signal.observeInterrupted({ print("Work interrupted") }) let date = NSDate().dateByAddingTimeInterval(2) QueueScheduler().scheduleAfter(date, action: { disposable.dispose() // Work interrupted, I'm done }) }
  87. 87. Disposing signal let producer = SignalProducer<String, NoError> { (observer, composite) in composite.addDisposable({ print("I'm done") }) let date = NSDate().dateByAddingTimeInterval(5) composite += QueueScheduler().scheduleAfter(date, action: { print("Doing my work") // Not printed }) } producer.startWithSignal { (signal, disposable) in let signalDisposable = signal.observeInterrupted({ print("Work interrupted") // Not printed }) let date = NSDate().dateByAddingTimeInterval(2) QueueScheduler().scheduleAfter(date, action: { signalDisposable!.dispose() }) let date2 = NSDate().dateByAddingTimeInterval(4) QueueScheduler().scheduleAfter(date2, action: { disposable.dispose() }) }
  88. 88. Closures
  89. 89. What's the result? var value = 10 let closure = { let date = NSDate().dateByAddingTimeInterval(2) QueueScheduler().scheduleAfter(date, action: { print(value) }) } closure() value = 20
  90. 90. Captured value var value = 10 let closure = { [value] in let date = NSDate().dateByAddingTimeInterval(2) QueueScheduler().scheduleAfter(date, action: { print(value) }) } closure() value = 20
  91. 91. Weak, Strong, Unowned...
  92. 92. Unowned let closure = { [unowned self] in self.label.text = "test" }
  93. 93. Weak let closure = { [weak self] in guard let weakSelf = self else { return } self.label.text = "test" }
  94. 94. Rex
  95. 95. UIButton let cocoaAction = CocoaAction(action) { _ in } //without Rex button.addTarget(cocoaAction, action: CocoaAction.selector, forControlEvents: .TouchUpInside) //with Rex extensions button.rex_pressed.value = cocoaAction
  96. 96. UITextField, UILabel, MutableProperty var titleValue = MutableProperty<String?>(nil) //without Rex textField.rac_textSignal().subscribeNext { self.titleValue.value = $0 as? String } titleValue.producer.startWithNext { self.label.text = $0 self.label.hidden = $0?.characters.count < 5 } //with Rex titleValue <~ textField.rex_text titleLabel.rex_text <~ titleValue titleLabel.rex_hidden <~ titleValue.producer.map( { $0?.characters.count < 5 } )
  97. 97. Let's see it in action

×