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

ReactiveCocoa workshop

  • 1.
  • 2.
    About me Eliasz Sawicki Blog:http://eluss.github.io/ Twitter: @EliSawic
  • 3.
  • 4.
  • 5.
  • 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.
  • 8.
    Reactive Programming • Workingwith asynchronous dataflow • Reacting to state changes
  • 9.
  • 10.
  • 11.
  • 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.
  • 16.
    Stateful var value =0 func increment() { value += 1 }
  • 17.
    Stateless func increment(value: Int)-> Int { return value + 1 }
  • 18.
  • 19.
  • 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.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 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 • Passesany 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.
  • 33.
    Basic signal Signal<String, NSError>{ (observer) -> Disposable? in observer.sendNext("test") observer.sendCompleted() return ActionDisposable(action: { print("Signal disposed") }) }
  • 34.
  • 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.
  • 37.
    What is it? •Represents tasks • Creates signals • Performs side effects • Does not start it's work if not started
  • 38.
    Injecting side effects letproducer = 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.
  • 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.
  • 43.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 55.
  • 56.
  • 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.
  • 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.
  • 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.
  • 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.
  • 67.
  • 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.
  • 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.
  • 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 letproducer = 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.
  • 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.
  • 83.
  • 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.
  • 89.
    What's the result? varvalue = 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.
  • 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.
  • 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 vartitleValue = 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 itin action