GithubHelp home page GithubHelp logo

reactkit's Introduction

ReactKit Circle CI

Swift Reactive Programming.

How to install

See Wiki page.

Example

For UI Demo, please see ReactKit/ReactKitCatalog.

Key-Value Observing

// create stream via KVO
self.obj1Stream = KVO.stream(obj1, "value")

// bind stream via KVC (`<~` as binding operator)
(obj2, "value") <~ self.obj1Stream

XCTAssertEqual(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()).

self.obj1Stream = nil   // release stream & its bindings

obj1.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.

NSNotification

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 e.g. map(f: T -> U) to convert it.

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

Target-Action

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

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

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

// REACT: enable/disable okButton
(self.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:

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<T>.Producer (Void -> Stream<T>) rather than plain Stream<T>. In this case, you can use |>> streamProducer-pipelining operator instead.

// first, wrap stream with closure
let timerProducer: Void -> Stream<Int> = {
    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 as well (thanks to @autoclosure).

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<U>)
      • 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<Stream<T>>)

    • mergeInner(nestedStream)
    • concatInner(nestedStream)
    • switchLatestInner(nestedStream)
  • For Stream Producer (Void -> Stream<T>)

    • 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

References

Licence

MIT

reactkit's People

Contributors

inamiy avatar robertjpayne avatar tokorom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactkit's Issues

Array operators return tuple instead

I have a use case where each signal has a different data type. After some transformation is done to each signal, they will be sent out together as network request. Since their types are different I can't put them in one array. As it stands, array operators like merge2All require all signals to be of the same type. Sure I can cast them to AnyObject but that's just way too verbose, and I will have to cast them back in order to send over network.

I think it's better to use tuple instead of array, in which case any types are allowed.

Signal for selectors?

How would I create a signal for invocation of an arbitrary selector? I'd like to react to touch events on a UIView, and because they are neither gestures nor control events, I'd like to generate signals every time the message 'sendEvent:' is sent to the view's window and filter and react to that.

I really like the tidy API BTW.

SwiftTask 2.6.1 prevents ReactKit from building when using Carthage

So far I have tried using ReactKit 0.7.0, 0.6.1 and 0.6.0. They all failed to build with an error because of SwiftTask when using the command carthage update. The error comes down to these two lines in my build log:

/Users/UserName/Projects/ProjectName/Carthage/Checkouts/ReactKit/Carthage/Checkouts/SwiftTask/SwiftTask/SwiftTask.swift:758:5: error: use of unresolved identifier 'objc_sync_enter'
    objc_sync_enter(object)
    ^
/Users/UserName/Projects/ProjectName/Carthage/Checkouts/ReactKit/Carthage/Checkouts/SwiftTask/SwiftTask/SwiftTask.swift:760:5: error: use of unresolved identifier 'objc_sync_exit'
    objc_sync_exit(object)
    ^

Add ignoreNil function

Lovely work, @inamiy. Even though I've been a huge RAC fan for a long time, now I'm planning to use ReactKit for my next app (specially for how it focuses a lot in making KVO smooth).

However, there's something from RAC that I think would make a great addition here: ignoreNil. As simple as it is, filter { $0 != nil } isn't as compelling and generates stress when reading code. Plus I find this to be a repetitive pattern in my codebases, so I think it's a good idea to have a specific function for it within the framework.

What do you think? I'm up for writing this function myself and PR once I'm done 😄

KVO signals don't pull initial value

Maybe it's intentional but I'm used to ReactiveCocoa where the KVO signals take the initial value when they are setup.

Thus when you setup the signal it'll fire the signal the first time by pulling the value.

2-way bindings?

Is it possible to keep a property of 1 object synchronised with a property of another, while avoiding the 'infinite echo' situation with naive 2-way observations?

I'm trying to make a model object's property synchronised with a text field. I can create a stream of the property values to bind to the value of the text field, but then I'd somehow need to make the stream of text field values set the model's property without triggering the previous binding.

Making Stream error generic

Currently Streams supply generic values, but the error is cocoa NSError.

I think it would be more future-proof and user friendly to support generic errors. Many times when dealing with Swift codes, the errors are simple enums.

What do you think?

Signals auto deinit upon creation?

Hi,
I can't seem to be able to create a signal using the init closure.. whenever I create a signal it automatically goes into failure block.. here's a test signal:

let signal = Signal<String> { (progress, fulfill, reject, configure) -> Void in

            progress("pffft, lets see")
            println("created signal successfully")

            }.progress({ (oldProgress, newProgress) -> Void in
                println(newProgress)
            })
            .success { value -> Void in
                println("singal is finished")
            }.failure({ (error, isCancelled) -> Void in
                println(error!.localizedDescription)
                println("is cancelled: \(isCancelled)")
            })

error!.localizedDescription is printing: Signal=DefaultSignal is cancelled via deinit.
Am I doing something wrong? or is there something wrong in the framework (which I doubt actually)?

Replaylast or similar needed

It would be good to have a signal which executes only one time, and then it returns same result.

I understand it's not 'pure', but it is really useful to build applications, specially when you deal with network requests and avoiding duplicate work.

Do you have any suggestions on how to best achieve this with current API?

Push latest tag to CocoaPods

Hi there,

CocoaPods lists 0.9.0 as the latest version. Would you mind pushing the latest tag when you get a free moment?

Love the project!

asStream returns nil value

In one class I have these

public var nickname: NSString?
private var nicknameSignal: Stream<NSString?>!
public init(){
    nicknameSignal = KVO.stream(self, "nickname")
            |> asStream(NSString?)
    nicknameSignal ~> println
}

In another class I have this code which observes on a textfield and sends the signal to nickname.

(account, "nickname") <~ nicknameFieldSignal!

However, the result from println is always nil. But if I map the value of nicknameSignal to the forced unwrap version first then the signal prints the correct value.

    nicknameSignal = KVO.stream(self, "nickname")
            |> map { $0! }
            |> asStream(NSString?)

Semantic operators

I like the simplicity of this project but it is incomplete without support for combining operations like zip, combineLatest, which are pretty essential. The any function looks like it's merge (which could be used as the basis for these other combining functions), but in other libraries any is a boolean operation. The different definitions for map should be named semantically like scan, flatMap, etc.

Do you have plans to support more of the operations from Rx? I am not smart enough to do the work myself

Support CocoaPods

Why aren't you supporting cocoapods? all major libraries are supporting cocoapods.. it would add up to the awesome library you have here!
It just requires use_framework, I don't see why not?

home page example code not working?

env: Xcode Version 7.0.1 (7A1001)
following along the home page:

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        let button = UIButton(frame: CGRectMake(40, 40, 40, 40))
        self.view.addSubview(button)

        button.backgroundColor = UIColor.redColor()

        let buttonStream = button.buttonStream("OK")

        ^{ print($0) } <~ buttonStream
 }

but nothing happened

Installation instruction

What's the proper way of installing this framework? I tried Cocoapods but it seems to be a much older version than what I find here.

Cannot submit to the app store

The ReactKit framework (built with Carthage) includes a Frameworks folder containing the Swift standard library, which prevents apps from being submitted to the app store:

screen shot 2015-06-12 at 12 47 48 pm

Deleting the Frameworks folder resolves the issue, but it would be nice to not have this step.

I'll submit a pull request if I can figure out the reason, but after a bit of digging I came up empty.

Question: Motivation of this Repo

First of all great work. Your project looks really awesome and must have been hard work to bring out such a wonderful project.

But want I would like to know is the following.
Why did you do it if there is something like Reactive Cocoa out there?
What sets it apart from Reactive Cocoa?
Could you not have contributed to Reactive Cocoa and bring in your own ideas?
What are your plans in the future? Will you be able to continue maintenance and support of Reactkit?

Just a few questions off of my head and I would really be happy to have these anwered.
Others might be interested in the answers as well.

Take care

Swift 2.0 Support

Currently working on swift/2.0 branch 🌱

  • Use ErrorType or generic Error instead of NSError #32
  • Rename stream operations (repeat, catch)

Update after executing <~

let stream = Stream.once("foo").ownedBy(self)
println <~ stream
println <~ stream

I wonder if it's correct behaviour, that println will be called once. If I now did something like:

(label,"text") <~ stream

The label would never change.

question - How to react?

I want a simple stream:

Stream.sequence([1,2,3,4])
            |> map { (n: Int) -> Void in
                return n * 2
            }
            |> react { // use of unresolved identifier 'react'
                println($0)
            }

But it said "use of unresolved identifier 'react'". I was confused how to apply react?

Also is it possible to construct a stream and dispatch change manually? E.g.

let stream = Stream<String>()
stream.map { ... }
    .react { ... }

// later on
stream.dispatch("change")

carthage update --use-submodules fails with an error

When I'm trying to call carthage update --use-submodules it returns error:

*** Fetching ReactKit
*** Fetching SwiftTask
*** Checking out SwiftTask at "2.6.3"
*** Checking out ReactKit at "0.7.1"
fatal: Not a git repository (or any of the parent directories): .git

The Carfile is pretty simple:

github "ReactKit/ReactKit"

Question: How to concatenate streams that terminate?

Hi!

Again thanks for you work on the lib :) I'm still learning Reactive Programming, and I wonder if you could assist me on how to do something I encounter all the time.

Let's say you have one stream, which does some asynchronous work, gives a successful value and fulfills.

And then somewhere else you want to continue doing something after this, with another stream.

If you connect both streams with flatMap, the second stream will terminate as well, which is not what I intended.

What do you recommend to use in this case?

Thanks!

buttonSignal Missing argument for parameter #1 in call

Here's working fine

self.buttonSignal = self.button.buttonSignal("OK")

But, When I try...

self.buttonSignal = self.button.buttonSignal()

I got...

ViewController.swift:87:58: Missing argument for parameter #1 in call

So the question is that possible to "not send" any arg?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.