Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ReactKit/ReactKit
Swift Reactive Programming.
https://github.com/ReactKit/ReactKit
Last synced: 14 days ago
JSON representation
Swift Reactive Programming.
- Host: GitHub
- URL: https://github.com/ReactKit/ReactKit
- Owner: ReactKit
- License: mit
- Created: 2014-09-29T14:09:19.000Z (about 10 years ago)
- Default Branch: swift/2.0
- Last Pushed: 2015-09-23T05:36:44.000Z (about 9 years ago)
- Last Synced: 2024-10-16T08:01:18.076Z (23 days ago)
- Language: Swift
- Size: 1.08 MB
- Stars: 1,193
- Watchers: 47
- Forks: 40
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-swift-cn - ReactKit - Swift Reactive Programming. (Libs / Events)
- awesome-swift - ReactKit - Swift Reactive Programming. (Extensions)
README
ReactKit [![Circle CI](https://circleci.com/gh/ReactKit/ReactKit/tree/swift%2F2.0.svg?style=svg)](https://circleci.com/gh/ReactKit/ReactKit/tree/swift%2F2.0)
========Swift Reactive Programming.
## How to install
See [Wiki page](https://github.com/ReactKit/ReactKit/wiki/How-to-install).
## Example
For UI Demo, please see [ReactKit/ReactKitCatalog](https://github.com/ReactKit/ReactKitCatalog).
### Key-Value Observing
```swift
// create stream via KVO
self.obj1Stream = KVO.stream(obj1, "value")// bind stream via KVC (`<~` as binding operator)
(obj2, "value") <~ self.obj1StreamXCTAssertEqual(obj1.value, "initial")
XCTAssertEqual(obj2.value, "initial")obj1.value = "REACT"
XCTAssertEqual(obj1.value, "REACT")
XCTAssertEqual(obj2.value, "REACT")
```To remove stream-bindings, just release `stream` itself (or call `stream.cancel()`).
```swift
self.obj1Stream = nil // release stream & its bindingsobj1.value = "Done"
XCTAssertEqual(obj1.value, "Done")
XCTAssertEqual(obj2.value, "REACT")
```If you want to observe changes in `Swift.Array` or `NSMutableArray`,
use `DynamicArray` feature in [Pull Request #23](https://github.com/ReactKit/ReactKit/pull/23).### NSNotification
```swift
self.stream = Notification.stream("MyNotification", obj1)
|> map { notification -> NSString? in
return "hello" // convert NSNotification? to NSString?
}(obj2, "value") <~ self.stream
```Normally, `NSNotification` itself is useless value for binding with other objects, so use [Stream Operations](#stream-operations) e.g. `map(f: T -> U)` to convert it.
To understand more about `|>` pipelining operator, see [Stream Pipelining](#stream-pipelining).
### Target-Action
```swift
// UIButton
self.buttonStream = self.button.buttonStream("OK")// UITextField
self.textFieldStream = self.textField.textChangedStream()^{ println($0) } <~ self.buttonStream // prints "OK" on tap
// NOTE: ^{ ... } = closure-first operator, same as `stream ~> { ... }`
^{ println($0) } <~ self.textFieldStream // prints textField.text on change
```### Complex example
The example below is taken from
- [iOS - ReactiveCocoaをかじってみた - Qiita](http://qiita.com/paming/items/9ac189ab0fe5b25fe722) (well-illustrated)
where it describes 4 `UITextField`s which enables/disables `UIButton` at certain condition (demo available in [ReactKit/ReactKitCatalog](https://github.com/ReactKit/ReactKitCatalog)):
```swift
let usernameTextStream = self.usernameTextField.textChangedStream()
let emailTextStream = self.emailTextField.textChangedStream()
let passwordTextStream = self.passwordTextField.textChangedStream()
let password2TextStream = self.password2TextField.textChangedStream()let allTextStreams = [usernameTextStream, emailTextStream, passwordTextStream, password2TextStream]
let combinedTextStream = allTextStreams |> merge2All
// create button-enabling stream via any textField change
self.buttonEnablingStream = combinedTextStream
|> map { (values, changedValue) -> NSNumber? inlet 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)
}// REACT: enable/disable okButton
(self.okButton, "enabled") <~ self.buttonEnablingStream!
```For more examples, please see XCTestCases.
## How it works
ReactKit is based on powerful [SwiftTask](https://github.com/ReactKit/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)](https://github.com/Reactive-Extensions) libraries which has a basic concept of "hot" and "cold" [observables](http://reactivex.io/documentation/observable.html), ReactKit gracefully integrated them into one **hot + paused (lazy) stream** `Stream` 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` |
| Generating | Cold Observable (cloneability) | `Void -> Stream`
(= `Stream.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](#stream-operations).
For example, a very common [incremental search technique](http://en.wikipedia.org/wiki/Incremental_search) using `searchTextStream` will look like this:
```swift
let 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.Producer` (`Void -> Stream`) rather than plain `Stream`. In this case, you can use `|>>` **streamProducer-pipelining operator** instead.
```swift
// first, wrap stream with closure
let timerProducer: Void -> Stream = {
return createTimerStream(interval: 1)
|> map { ... }
|> filter { ... }
}// then, use `|>>` (streamProducer-pipelining operator)
let 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](#stream-operations) as well (thanks to `@autoclosure`).
```swift
let repeatTimerProducer = createTimerStream(interval: 1)
|>> map { ... }
|>> filter { ... }
|>> repeat(3)
```## Functions
### Stream Operations
- For Single Stream
- Transforming
- `asStream(ValueType)`
- `map(f: T -> U)`
- `flatMap(f: T -> Stream)`
- `map2(f: (old: T?, new: T) -> U)`
- `mapAccumulate(initialValue, accumulator)` (alias: `scan`)
- `buffer(count)`
- `bufferBy(stream)`
- `groupBy(classifier: T -> Key)`
- Filtering
- `filter(f: T -> Bool)`
- `filter2(f: (old: T?, new: T) -> Bool)`
- `take(count)`
- `takeUntil(stream)`
- `skip(count)`
- `skipUntil(stream)`
- `sample(stream)`
- `distinct()`
- `distinctUntilChanged()`
- Combining
- `merge(stream)`
- `concat(stream)`
- `startWith(initialValue)`
- `combineLatest(stream)`
- `zip(stream)`
- `recover(stream)`
- Timing
- `delay(timeInterval)`
- `interval(timeInterval)`
- `throttle(timeInterval)`
- `debounce(timeInterval)`
- Collecting
- `reduce(initialValue, accumulator)`
- Other Utilities
- `peek(f: T -> Void)` (for injecting side effects e.g. debug-logging)
- `customize(...)`- For Array Streams
- `mergeAll(streams)`
- `merge2All(streams)` (generalized method for `mergeAll` & `combineLatestAll`)
- `combineLatestAll(streams)`
- `zipAll(streams)`- For Nested Stream (`Stream>`)
- `mergeInner(nestedStream)`
- `concatInner(nestedStream)`
- `switchLatestInner(nestedStream)`- For Stream Producer (`Void -> Stream`)
- `prestart(bufferCapacity)` (alias: `replay`)
- `times(count)`
- `retry(count)`### Helpers
- Creating
- `Stream.once(value)` (alias: `just`)
- `Stream.never()`
- `Stream.fulfilled()` (alias: `empty`)
- `Stream.rejected()` (alias: `error`)
- `Stream.sequence(values)` (a.k.a Rx.fromArray)
- `Stream.infiniteSequence(initialValue, iterator)` (a.k.a Rx.iterate)- Other Utilities
- `ownedBy(owner: NSObject)` (easy strong referencing to keep streams alive)## Dependencies
- [SwiftTask](https://github.com/ReactKit/SwiftTask)
## References
- [Introducing ReactKit // Speaker Deck](https://speakerdeck.com/inamiy/introducing-reactkit) (ver 0.3.0)
## Licence
[MIT](https://github.com/ReactKit/ReactKit/blob/master/LICENSE)