ReactKit
Swift Reactive Programming.
How to install
See Wiki page.
Example
For UI Demo, please see ReactKit/ReactKitCatalog.
Key-Value Observing
// create signal via KVO
self.obj1Signal = KVO.signal(obj1, "value")
// bind signal via KVC (<~ as binding operator)
(obj2, "value") <~ self.obj1Signal
XCTAssertEqual(obj1.value, "initial")
XCTAssertEqual(obj2.value, "initial")
obj1.value = "REACT"
XCTAssertEqual(obj1.value, "REACT")
XCTAssertEqual(obj2.value, "REACT")To remove signal-bindings, just release signal itself.
self.obj1Signal = nil // release signal & its bindings
obj1.value = "Done"
XCTAssertEqual(obj1.value, "Done")
XCTAssertEqual(obj2.value, "REACT")NSNotification
self.signal = Notification.signal("MyNotification", obj1).map { notification -> NSString? in
return "hello" // convert NSNotification? to NSString?
}
(obj2, "value") <~ self.signalNormally, NSNotification itself is useless value for binding with other objects, so use signal-operations e.g. map(f: T -> U) to convert it.
Target-Action
// UIButton
self.buttonSignal = self.button.buttonSignal("OK")
// UITextField
self.textFieldSignal = self.textField.textChangedSignal()
^{ println($0) } <~ self.buttonSignal // prints "OK" on tap
// NOTE: ^{ ... } = closure-first operator, same as `signal ~> { ... }`
^{ println($0) } <~ self.textFieldSignal // prints textField.text on changeComplex example
The example below is taken from
- iOS - ReactiveCocoaをかじってみた - Qiita (well-illustrated)
where it describes 4 UITextFields which enables/disables UIButton at certain condition:
let usernameTextSignal = self.usernameTextField.textChangedSignal()
let emailTextSignal = self.emailTextField.textChangedSignal()
let passwordTextSignal = self.passwordTextField.textChangedSignal()
let password2TextSignal = self.password2TextField.textChangedSignal()
let anyTextSignal = Signal.any([usernameTextSignal, emailTextSignal, passwordTextSignal, password2TextSignal])
// create button-enabling signal via any textField change
self.buttonEnablingSignal = anyTextSignal.map { (values, changedValue) -> NSNumber? in
let username: NSString? = values[0]?
let email: NSString? = values[1]?
let password: NSString? = values[2]?
let password2: NSString? = values[3]?
// validation
let buttonEnabled = username?.length > 0 && email?.length > 0 && password?.length >= MIN_PASSWORD_LENGTH && password? == password2?
// NOTE: use NSNumber because KVO does not understand Bool
return NSNumber(bool: buttonEnabled)
}
// REACT: enable/disable okButton
(self.okButton, "enabled") <~ self.buttonEnablingSignal!For more examples, please see XCTestCases.
How it works
ReactKit is based on powerful SwiftTask (JavaScript Promise-like) library, allowing to deliver multiple events (KVO, NSNotification, Target-Action, etc) continuously over time using its progress feature (<~ operator in ReactKit).
Unlike many Reactive Extensions (Rx) libraries including ReactiveCocoa which has a basic concept of "hot" and "cold" signals, ReactKit gracefully integrated them into one hot + paused (lazy) signal Signal<T> class as well as not adopting the concept of Disposable but using signal.cancel() feature instead.
Also in ReactKit, Rx's signal.subscribe(onNext, onError, onComplete) method is interpreted as signal.progress() (<~) and signal.then()/success()/failure() separately. When they are called on needs, they will start lazy evaluation.
Methods
Signal Operations
- Instance Methods
filter(f: T -> Bool)filter2(f: (old: T?, new: T) -> Bool)map(f: T -> U)flatMap(f: T -> Signal<U>)map2(f: (old: T?, new: T) -> U)-
mapAccumulate(initialValue, accumulator)(alias:scan) take(count)takeUntil(signal)skip(count)skipUntil(signal)merge(signal)concat(signal)startWith(initialValue)zip(signal)buffer(count)bufferBy(signal)delay(timeInterval)throttle(timeInterval)debounce(timeInterval)
- Class Methods
merge(signals)-
merge2(signals)(generalized method formerge&combineLatest) combineLatest(signals)concat(signals)zip(signals)
Helpers
-
asSignal(ValueType)(WARNING: currently works for non-Optional only) -
Signal.once(value)(alias:just) Signal.never()-
Signal.fulfilled()(alias:empty) -
Signal.rejected()(alias:error) -
Signal(values:)(a.k.a Rx.fromArray)
Dependencies
References
- Introducing ReactKit // Speaker Deck (ver 0.3.0)