Swift Reactive Programming.

Overview

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

Comments
  • Question: How to concatenate streams that terminate?

    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!

    bug 
    opened by DarthMike 7
  • Refactor code for Swift 1.2.

    Refactor code for Swift 1.2.

    This pull request is a BREAKING CHANGE where not only fixing for Swift 1.2 but also refactoring whole codes as follows:

    • [x] 1. Rename Signal<T> to Stream<T>
    • [x] 2. Stream-operation functions
      • [x] 1. Move functions outside of Stream<T> class for better type-constraints.
      • [x] 2. Rename e.g. merge, mergeAll, mergeInner & always use **All (stream-array) and **Inner (nested-stream) naming conventions
      • [x] 3. Add more useful operations
        • [x] distinct()
        • [x] distinctUntilChanged()
        • [x] reduce()
        • [x] interval()
        • [x] switchLatestInner()
        • [x] prestart() (a.k.a Rx.replay)
        • [x] catch()
        • [x] retry()
        • [x] repeat()
    • [x] 3. Add stream pipe operator |> and stream-producer pipe operator |>> in replace of dot-method-chaining syntax.
    • [x] 4. Add terminal reacting operator ~>! () to collect synchronously-emitted values (useful just like Java 8 Stream API)
    • [x] 5. Update README.md
    enhancement 
    opened by inamiy 7
  • SwiftTask 2.6.1 prevents ReactKit from building when using Carthage

    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)
        ^
    
    opened by isair 7
  • Fix AppStore submission by removing EMBEDDED_CONTENT_CONTAINS_SWIFT=YES.

    Fix AppStore submission by removing EMBEDDED_CONTENT_CONTAINS_SWIFT=YES.

    opened by inamiy 6
  • Making Stream error generic

    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?

    enhancement question 
    opened by DarthMike 5
  • Swift 1.2

    Swift 1.2

    Update to Swift 1.2. The swift-1.2 branch is behind a bit so I've started off master as that seems to have worked fine. This is for Xcode-6.3b4. All tests pass.

    This assumes you’d want to switch master to swift 1.2, of course.

    opened by finestructure 5
  • KVO signals don't pull initial value

    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.

    opened by robertjpayne 5
  • Replaylast or similar needed

    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?

    enhancement 
    opened by DarthMike 4
  • KVO signals should allow nil value placeholder

    KVO signals should allow nil value placeholder

    Because KVO emits NSNull values when the target value is nil we should allow the option to set a nil value placeholder.

    This allows cleaner binding without having to monitor for NSNull and map it to nil or a default value.

    opened by robertjpayne 4
  • Add more Rx semantic operators.

    Add more Rx semantic operators.

    This pull request is an improvement for #10 (Rx semantics), and also a BREAKING CHANGE to use different Task subclass as discussed in #12.

    Added

    • [x] fromTask (like fromPromise)
    • [x] combineLatest
    • [x] concat
    • [x] startWith
    • [x] zip
    • [x] delay
    • [x] once (just), fulfilled (empty), rejected (error), never

    Renamed

    • [x] flatMap
    • [x] mapAccumulate, scan (alias)
    • [x] takeUntil
    • [x] skipUntil
    • [x] Signal.merge2
    opened by inamiy 4
  • carthage update --use-submodules fails with an error

    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"
    
    opened by slabko 3
  • Add ignoreNil function

    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 😄

    opened by davdroman 2
  • 2-way bindings?

    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.

    opened by sohocoke 0
  • Update after executing <~

    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 
    opened by pszot-concise 1
  • Array operators return tuple instead

    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.

    enhancement 
    opened by taonos 1
Releases(0.12.0)
  • 0.12.0(Sep 23, 2015)

  • 0.11.4(Jul 26, 2015)

  • 0.11.3(Jun 24, 2015)

    • Improve KVO binding operator (<~) to let Stream work when T is non-ObjC String, Int, Float, Double. #46

      We can now use Stream<String?> instead of Stream<NSString?>.

    • Add UITextView+Stream #45

    Source code(tar.gz)
    Source code(zip)
  • 0.11.2(Jun 24, 2015)

  • 0.11.1(Jun 18, 2015)

  • 0.11.0(Jun 1, 2015)

    • [x] Add startAsync(), async(), asyncBackpressureBlock() (experimental) #36
    • [x] Add branch() (oneway-pipelining) #37
    • [x] Improve unbinding using Canceller (Rx.Disposable) #33, #35
    • [x] Fix catch() #34
    Source code(tar.gz)
    Source code(zip)
  • 0.10.0(Apr 23, 2015)

    BREAKING CHANGE (see #26 for more information)

    • [x] 1. Rename Signal<T> to Stream<T>
    • [x] 2. Stream-operation functions
      • [x] 1. Move functions outside of Stream<T> class for better type-constraints.
      • [x] 2. Rename e.g. merge, mergeAll, mergeInner & always use **All (stream-array) and **Inner (nested-stream) naming conventions
      • [x] 3. Add more useful operations
        • [x] distinct()
        • [x] distinctUntilChanged()
        • [x] reduce()
        • [x] interval()
        • [x] switchLatestInner()
        • [x] prestart() (a.k.a Rx.replay)
        • [x] catch()
        • [x] retry()
        • [x] repeat()
    • [x] 3. Add stream pipe operator |> and stream-producer pipe operator |>> in replace of dot-method-chaining syntax.
    • [x] 4. Add terminal reacting operator ~>! () to collect synchronously-emitted values (useful just like Java 8 Stream API)
    Source code(tar.gz)
    Source code(zip)
  • 0.9.0(Apr 9, 2015)

  • 0.8.0(Apr 14, 2015)

    • Add groupBy()
    • Add sample()
    • Add KVO.startingSignal() & KVO.detailedSignal()
    • Add DynamicArray feature
    • Fix KVO+NSNull crash
    • Less memory footprint (discarding update of progress value)
    Source code(tar.gz)
    Source code(zip)
  • 0.7.0(Apr 14, 2015)

  • 0.6.0(Jan 26, 2015)

  • 0.5.0(Jan 26, 2015)

  • 0.4.0(Jan 26, 2015)

Owner
ReactKit
Swift Reactive Programming Toolkit
ReactKit
A Swift framework for reactive programming.

CwlSignal An implementation of reactive programming. For details, see the article on Cocoa with Love, CwlSignal, a library for reactive programming. N

Matt Gallagher 304 Oct 25, 2022
Swift Reactive Programming.

ReactKit Swift Reactive Programming. How to install See Wiki page. Example For UI Demo, please see ReactKit/ReactKitCatalog. Key-Value Observing // cr

ReactKit 1.2k Nov 6, 2022
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.

About Architecture concerns RxReduce Installation The key principles How to use RxReduce Tools and dependencies Travis CI Frameworks Platform Licence

RxSwift Community 125 Jan 29, 2022
Simple and lightweight Functional Reactive Coding in Swift for the rest of us

The simplest Observable<T> implementation for Functional Reactive Programming you will ever find. This library does not use the term FRP (Functional R

Jens Ravens 1.1k Jan 3, 2023
A reactive wrapper built around UIImagePickerController.

RxMediaPicker RxMediaPicker is a RxSwift wrapper built around UIImagePickerController consisting in a simple interface for common actions like picking

RxSwift Community 180 Apr 24, 2022
RxSwift reactive wrapper for view gestures

RxGesture Usage To run the example project, clone the repo, in the Example folder open RxGesture.xcworkspace. You might need to run pod install from t

RxSwift Community 1.3k Dec 30, 2022
Reactive Keyboard in iOS

RxKeyboard RxKeyboard provides a reactive way of observing keyboard frame changes. Forget about keyboard notifications. It also perfectly works with U

RxSwift Community 1.4k Dec 29, 2022
Reactive WebSockets

RxWebSocket Reactive extensions for websockets. A lightweight abstraction layer over Starscream to make it reactive. Installation RxWebSocket is avail

Flávio Caetano 57 Jul 22, 2022
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
Store-App - Store app made for IOS using Swift programming language

Store-App Store app views products, cart, and using login from https://fakestore

Anas Khalil 2 Jan 1, 2022
Redux for Swift - a predictable state container for Swift apps

Merge / deprecation announcement: ReduxKit and Swift-Flow have joined forces! The result is ReSwift. The nitty gritty: We decided to deprecate ReduxKi

null 613 Jan 3, 2023
Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features.

swift-unidirectional-flow Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features. struct SearchState: Equatabl

Majid Jabrayilov 104 Dec 26, 2022
RxSwift extentions for Swift optionals and "Occupiable" types

RxOptional RxSwift extentions for Swift optionals and "Occupiable" types. Usage All operators are available on Driver as well unless otherwise marked.

Thane Gill 8 Jun 28, 2020
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Dec 25, 2022
RxSwift wrapper around the elegant HTTP networking in Swift Alamofire

RxAlamofire RxAlamofire is a RxSwift wrapper around the elegant HTTP networking in Swift Alamofire. Getting Started Wrapping RxSwift around Alamofire

RxSwift Community 1.6k Jan 3, 2023
An array class implemented in Swift that can be observed using ReactiveCocoa's Signals

ReactiveArray An array class implemented in Swift that can be observed using ReactiveCocoa's Signals. Installation Carthage Add the following to your

Wolox 53 Jan 29, 2022
Predictable state container for Swift too

ReduxSwift ReduxSwift is a minimal Swift port of Redux, a popular JavaScript library for application state management. Functionality Centralized State

Lucas Sunsi Abreu 38 Oct 6, 2020
Aftermath is a stateless message-driven micro-framework in Swift

Aftermath is a stateless message-driven micro-framework in Swift, which is based on the concept of the unidirectional data flow architecture.

HyperRedink 70 Dec 24, 2021
🔄 Unidirectional data flow in Swift.

Reactor Reactor is a framework for making more reactive applications inspired by Elm, Redux, and recent work on ReSwift. It's small and simple (just o

Reactor 175 Jul 9, 2022