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).
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
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")
}
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
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)
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
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)
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"
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
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()
})
}
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