wantedly/ReactKit

Name: ReactKit

Owner: Wantedly, Inc.

Description: Swift Reactive Programming.

Created: 2015-07-17 02:59:48.0

Updated: 2016-12-26 14:37:40.0

Pushed: 2015-07-17 05:45:35.0

Homepage: null

Size: 494

Language: Swift

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

ReactKit

Swift Reactive Programming.

Ver 0.10.0 Changelog (2015/04/23)

This is a breaking change. See #26 and Ver 0.10.0 Release Notes for more information.

How to install

See Wiki page.

Example

For UI Demo, please see ReactKit/ReactKitCatalog.

Key-Value Observing
reate stream via KVO
.obj1Stream = KVO.stream(obj1, "value")

ind stream via KVC (`<~` as binding operator)
2, "value") <~ self.obj1Stream

ssertEqual(obj1.value, "initial")
ssertEqual(obj2.value, "initial")

.value = "REACT"

ssertEqual(obj1.value, "REACT")
ssertEqual(obj2.value, "REACT")

To remove stream-bindings, just release stream itself (or call stream.cancel()).

.obj1Stream = nil   // release stream & its bindings

.value = "Done"

ssertEqual(obj1.value, "Done")
ssertEqual(obj2.value, "REACT")

If you want to observe changes in Swift.Array or NSMutableArray, use DynamicArray feature in Pull Request #23.

NSNotification
.stream = Notification.stream("MyNotification", obj1)
|> map { notification -> NSString? in
    return "hello" // convert NSNotification? to NSString?
}

2, "value") <~ self.stream

Normally, NSNotification itself is useless value for binding with other objects, so use Stream Operations e.g. map(f: T -> U) to convert it.

To understand more about |> pipelining operator, see Stream Pipelining.

Target-Action
IButton
.buttonStream = self.button.buttonStream("OK")

ITextField
.textFieldStream = self.textField.textChangedStream()

rintln($0) } <~ self.buttonStream     // prints "OK" on tap

OTE: ^{ ... } = closure-first operator, same as `stream ~> { ... }`
rintln($0) } <~ self.textFieldStream  // prints textField.text on change
Complex example

The example below is taken from

where it describes 4 UITextFields which enables/disables UIButton at certain condition (demo available in ReactKit/ReactKitCatalog):

usernameTextStream = self.usernameTextField.textChangedStream()
emailTextStream = self.emailTextField.textChangedStream()
passwordTextStream = self.passwordTextField.textChangedStream()
password2TextStream = self.password2TextField.textChangedStream()

allTextStreams = [usernameTextStream, emailTextStream, passwordTextStream, password2TextStream]

combinedTextStream = allTextStreams |> merge2All

reate button-enabling stream via any textField change
.buttonEnablingStream = combinedTextStream
|> map { (values, changedValue) -> NSNumber? in

    let username: NSString? = values[0] ?? nil
    let email: NSString? = values[1] ?? nil
    let password: NSString? = values[2] ?? nil
    let password2: NSString? = values[3] ?? nil

    // 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)
}

EACT: enable/disable okButton
f.okButton, "enabled") <~ self.buttonEnablingStream!

For more examples, please see XCTestCases.

How it works

ReactKit is based on powerful SwiftTask (JavaScript Promise-like) library, allowing to start & deliver multiple events (KVO, NSNotification, Target-Action, etc) continuously over time using its resume & progress feature (react() or <~ operator in ReactKit).

Unlike Reactive Extensions (Rx) libraries which has a basic concept of “hot” and “cold” observables, ReactKit gracefully integrated them into one hot + paused (lazy) stream Stream<T> class. Lazy streams will be auto-resumed via react() & <~ operator.

Here are some differences in architecture:

| | Reactive Extensions (Rx) | ReactKit | |:—:|:—:|:—:| | Basic Classes | Hot Observable (broadcasting)
Cold Observable (laziness) | Stream<T> | | Generating | Cold Observable (cloneability) | Void -> Stream<T>
(= Stream<T>.Producer) | | Subscribing | observable.subscribe(onNext, onError, onComplete) | stream.react {...}.then {...}
(method-chainable) | | Pausing | pausableObservable.pause() | stream.pause() | | Disposing | disposable.dispose() | stream.cancel() |

Stream Pipelining

Streams can be composed by using |> stream-pipelining operator and Stream Operations.

For example, a very common incremental search technique using searchTextStream will look like this:

searchResultsStream: Stream<[Result]> = searchTextStream
|> debounce(0.3)
|> distinctUntilChanged
|> map { text -> Stream<[Result]> in
    return API.getSearchResultsStream(text)
}
|> switchLatestInner

There are some scenarios (e.g. repeat()) when you want to use a cloneable Stream<T>.Producer (Void -> Stream<T>) rather than plain Stream<T>. In this case, you can use |>> streamProducer-pipelining operator instead.

irst, wrap stream with closure
timerProducer: Void -> Stream<Int> = {
return createTimerStream(interval: 1)
    |> map { ... }
    |> filter { ... }


hen, use `|>>`  (streamProducer-pipelining operator)
repeatTimerProducer = timerProducer |>> repeat(3)

But in the above case, wrapping with closure will always become cumbersome, so you can also use |>> operator for Stream & Stream Operations as well (thanks to @autoclosure).

repeatTimerProducer = createTimerStream(interval: 1)
|>> map { ... }
|>> filter { ... }
|>> repeat(3)
Functions
Stream Operations
Helpers
Dependencies
References
Licence

MIT


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.