More Related Content

ReactiveCocoa workshop

  1. Building iOS apps with ReactiveCocoa
  2. About me Eliasz Sawicki Blog: http://eluss.github.io/ Twitter: @EliSawic
  3. Let's begin
  4. ReactiveCocoa
  5. Functional Reactive Programming
  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. Reactive Programming
  8. Reactive Programming • Working with asynchronous dataflow • Reacting to state changes
  9. Functional Programming
  10. Functional Programming • Immutable
  11. assert(f(x) == f(x))
  12. A person class Person { var name: String init(name: String) { self.name = name } }
  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. 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. Functional Programming • Immutable • Stateless
  16. Stateful var value = 0 func increment() { value += 1 }
  17. Stateless func increment(value: Int) -> Int { return value + 1 }
  18. Declarative
  19. Imperative vs Declarative
  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. Declarative let array = [0, 1, 2, 3, 4, 5] let evenNumbers = array.filter { $0 % 2 == 0 }
  22. Back to ReactiveCocoa
  23. Event streams
  24. Event Stream
  25. Event
  26. Non-Terminating • Next Terminating • Completed • Failed • Interrupted
  27. Observer
  28. Signal
  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. Signal's lifetime • Passes any number of Next events • "Dies" when terminating event arrives • Any new observer will receive Interrupted event
  31. Observing signal.observe { (event) in print(event) } signal.observeNext { (value) in print(value) } signal.observeCompleter { print("Completed") }
  32. Creating Signals
  33. Basic signal Signal<String, NSError> { (observer) -> Disposable? in observer.sendNext("test") observer.sendCompleted() return ActionDisposable(action: { print("Signal disposed") }) }
  34. Pipe
  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. SignalProducer
  37. What is it? • Represents tasks • Creates signals • Performs side effects • Does not start it's work if not started
  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. Creating Signal Producers
  40. Basic Signal Producer SignalProducer<String, NSError> { (observer, composite) in composite.addDisposable({ print("Clearing work") }) observer.sendNext("In Progres...") observer.sendCompleted() }
  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. Manipulating signals
  43. Map
  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. Filter
  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. Aggregating
  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. Skip repeats
  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. Skip until
  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. 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. Manipulating multiple signals
  55. Combine latest
  56. Combine latest
  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. Zip
  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. Merge
  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. Handling errors
  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. 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. Promoting let (numbersSignal, numbersObserver) = Signal<Int, NoError>.pipe() let (lettersSignal, lettersObserver) = Signal<String, NSError>.pipe() numbersSignal .promoteErrors(NSError) .combineLatestWith(lettersSignal)
  66. Mapping errors
  67. Properties
  68. Properties • AnyProperty • ConstantProperty • MutableProperty • DynamicProperty
  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. DynamicProperty let textProperty = DynamicProperty(object: textField, keyPath: "text") textProperty.producer.startWithNext { (text) in print(text) } textProperty.value = "Textfield text"
  71. Bindings
  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. Action
  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. Create signal producer let producer = action.apply(1)
  76. Execute action prodcuer.startWithSignal { (signal, disposable ) in signal.observeNext({ (value) in print("(value)") }) signal.observeFailed({ (actionError) in print("(actionError)") }) }
  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. CocoaAction
  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. 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. Schedulers • SchedulerType • ImmediateScheduler • UIScheduler • DateSchedulerType • QueueScheduler • TestScheduler
  82. Memory Management
  83. Disposables
  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. 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. 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. 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. Closures
  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. Captured value var value = 10 let closure = { [value] in let date = NSDate().dateByAddingTimeInterval(2) QueueScheduler().scheduleAfter(date, action: { print(value) }) } closure() value = 20
  91. Weak, Strong, Unowned...
  92. Unowned let closure = { [unowned self] in self.label.text = "test" }
  93. Weak let closure = { [weak self] in guard let weakSelf = self else { return } self.label.text = "test" }
  94. Rex
  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. 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. Let's see it in action