A library for reactive and unidirectional Swift applications

Last update: Jun 22, 2022

ReactorKit

Swift CocoaPods Platform CI Codecov

ReactorKit is a framework for a reactive and unidirectional Swift application architecture. This repository introduces the basic concept of ReactorKit and describes how to build an application using ReactorKit.

You may want to see the Examples section first if you'd like to see the actual code. For an overview of ReactorKit's features and the reasoning behind its creation, you may also check the slides from this introductory presentation over at SlideShare.

Table of Contents

Basic Concept

ReactorKit is a combination of Flux and Reactive Programming. The user actions and the view states are delivered to each layer via observable streams. These streams are unidirectional: the view can only emit actions and the reactor can only emit states.

flow

Design Goal

  • Testability: The first purpose of ReactorKit is to separate the business logic from a view. This can make the code testable. A reactor doesn't have any dependency to a view. Just test reactors and test view bindings. See Testing section for details.
  • Start Small: ReactorKit doesn't require the whole application to follow a single architecture. ReactorKit can be adopted partially, for one or more specific views. You don't need to rewrite everything to use ReactorKit on your existing project.
  • Less Typing: ReactorKit focuses on avoiding complicated code for a simple thing. ReactorKit requires less code compared to other architectures. Start simple and scale up.

View

A View displays data. A view controller and a cell are treated as a view. The view binds user inputs to the action stream and binds the view states to each UI component. There's no business logic in a view layer. A view just defines how to map the action stream and the state stream.

To define a view, just have an existing class conform a protocol named View. Then your class will have a property named reactor automatically. This property is typically set outside of the view.

class ProfileViewController: UIViewController, View {
  var disposeBag = DisposeBag()
}

profileViewController.reactor = UserViewReactor() // inject reactor

When the reactor property has changed, bind(reactor:) gets called. Implement this method to define the bindings of an action stream and a state stream.

func bind(reactor: ProfileViewReactor) {
  // action (View -> Reactor)
  refreshButton.rx.tap.map { Reactor.Action.refresh }
    .bind(to: reactor.action)
    .disposed(by: self.disposeBag)

  // state (Reactor -> View)
  reactor.state.map { $0.isFollowing }
    .bind(to: followButton.rx.isSelected)
    .disposed(by: self.disposeBag)
}

Storyboard Support

Use StoryboardView protocol if you're using a storyboard to initialize view controllers. Everything is same but the only difference is that the StoryboardView performs a binding after the view is loaded.

let viewController = MyViewController()
viewController.reactor = MyViewReactor() // will not executes `bind(reactor:)` immediately

class MyViewController: UIViewController, StoryboardView {
  func bind(reactor: MyViewReactor) {
    // this is called after the view is loaded (viewDidLoad)
  }
}

Reactor

A Reactor is an UI-independent layer which manages the state of a view. The foremost role of a reactor is to separate control flow from a view. Every view has its corresponding reactor and delegates all logic to its reactor. A reactor has no dependency to a view, so it can be easily tested.

Conform to the Reactor protocol to define a reactor. This protocol requires three types to be defined: Action, Mutation and State. It also requires a property named initialState.

class ProfileViewReactor: Reactor {
  // represent user actions
  enum Action {
    case refreshFollowingStatus(Int)
    case follow(Int)
  }

  // represent state changes
  enum Mutation {
    case setFollowing(Bool)
  }

  // represents the current view state
  struct State {
    var isFollowing: Bool = false
  }

  let initialState: State = State()
}

An Action represents a user interaction and State represents a view state. Mutation is a bridge between Action and State. A reactor converts the action stream to the state stream in two steps: mutate() and reduce().

flow-reactor

mutate()

mutate() receives an Action and generates an Observable<Mutation>.

func mutate(action: Action) -> Observable<Mutation>

Every side effect, such as an async operation or API call, is performed in this method.

func mutate(action: Action) -> Observable<Mutation> {
  switch action {
  case let .refreshFollowingStatus(userID): // receive an action
    return UserAPI.isFollowing(userID) // create an API stream
      .map { (isFollowing: Bool) -> Mutation in
        return Mutation.setFollowing(isFollowing) // convert to Mutation stream
      }

  case let .follow(userID):
    return UserAPI.follow()
      .map { _ -> Mutation in
        return Mutation.setFollowing(true)
      }
  }
}

reduce()

reduce() generates a new State from a previous State and a Mutation.

func reduce(state: State, mutation: Mutation) -> State

This method is a pure function. It should just return a new State synchronously. Don't perform any side effects in this function.

func reduce(state: State, mutation: Mutation) -> State {
  var state = state // create a copy of the old state
  switch mutation {
  case let .setFollowing(isFollowing):
    state.isFollowing = isFollowing // manipulate the state, creating a new state
    return state // return the new state
  }
}

transform()

transform() transforms each stream. There are three transform() functions:

func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>

Implement these methods to transform and combine with other observable streams. For example, transform(mutation:) is the best place for combining a global event stream to a mutation stream. See the Global States section for details.

These methods can be also used for debugging purposes:

func transform(action: Observable<Action>) -> Observable<Action> {
  return action.debug("action") // Use RxSwift's debug() operator
}

Advanced

Global States

Unlike Redux, ReactorKit doesn't define a global app state. It means that you can use anything to manage a global state. You can use a BehaviorSubject, a PublishSubject or even a reactor. ReactorKit doesn't force to have a global state so you can use ReactorKit in a specific feature in your application.

There is no global state in the Action → Mutation → State flow. You should use transform(mutation:) to transform the global state to a mutation. Let's assume that we have a global BehaviorSubject which stores the current authenticated user. If you'd like to emit a Mutation.setUser(User?) when the currentUser is changed, you can do as following:

var currentUser: BehaviorSubject<User> // global state

func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
  return Observable.merge(mutation, currentUser.map(Mutation.setUser))
}

Then the mutation will be emitted each time the view sends an action to a reactor and the currentUser is changed.

View Communication

You must be familiar with callback closures or delegate patterns for communicating between multiple views. ReactorKit recommends you to use reactive extensions for it. The most common example of ControlEvent is UIButton.rx.tap. The key concept is to treat your custom views as UIButton or UILabel.

view-view

Let's assume that we have a ChatViewController which displays messages. The ChatViewController owns a MessageInputView. When an user taps the send button on the MessageInputView, the text will be sent to the ChatViewController and ChatViewController will bind in to the reactor's action. This is an example MessageInputView's reactive extension:

extension Reactive where Base: MessageInputView {
  var sendButtonTap: ControlEvent<String> {
    let source = base.sendButton.rx.tap.withLatestFrom(...)
    return ControlEvent(events: source)
  }
}

You can use that extension in the ChatViewController. For example:

messageInputView.rx.sendButtonTap
  .map(Reactor.Action.send)
  .bind(to: reactor.action)

Testing

ReactorKit has a built-in functionality for a testing. You'll be able to easily test both a view and a reactor with a following instruction.

What to test

First of all, you have to decide what to test. There are two things to test: a view and a reactor.

  • View
    • Action: is a proper action sent to a reactor with a given user interaction?
    • State: is a view property set properly with a following state?
  • Reactor
    • State: is a state changed properly with an action?

View testing

A view can be tested with a stub reactor. A reactor has a property stub which can log actions and force change states. If a reactor's stub is enabled, both mutate() and reduce() are not executed. A stub has these properties:

var state: StateRelay<Reactor.State> { get }
var action: ActionSubject<Reactor.Action> { get }
var actions: [Reactor.Action] { get } // recorded actions

Here are some example test cases:

func testAction_refresh() {
  // 1. prepare a stub reactor
  let reactor = MyReactor()
  reactor.isStubEnabled = true

  // 2. prepare a view with a stub reactor
  let view = MyView()
  view.reactor = reactor

  // 3. send an user interaction programatically
  view.refreshControl.sendActions(for: .valueChanged)

  // 4. assert actions
  XCTAssertEqual(reactor.stub.actions.last, .refresh)
}

func testState_isLoading() {
  // 1. prepare a stub reactor
  let reactor = MyReactor()
  reactor.isStubEnabled = true

  // 2. prepare a view with a stub reactor
  let view = MyView()
  view.reactor = reactor

  // 3. set a stub state
  reactor.stub.state.value = MyReactor.State(isLoading: true)

  // 4. assert view properties
  XCTAssertEqual(view.activityIndicator.isAnimating, true)
}

Reactor testing

A reactor can be tested independently.

func testIsBookmarked() {
  let reactor = MyReactor()
  reactor.action.onNext(.toggleBookmarked)
  XCTAssertEqual(reactor.currentState.isBookmarked, true)
  reactor.action.onNext(.toggleBookmarked)
  XCTAssertEqual(reactor.currentState.isBookmarked, false)
}

Sometimes a state is changed more than one time for a single action. For example, a .refresh action sets state.isLoading to true at first and sets to false after the refreshing. In this case it's difficult to test state.isLoading with currentState so you might need to use RxTest or RxExpect. Here is an example test case using RxSwift:

func testIsLoading() {
  // given
  let scheduler = TestScheduler(initialClock: 0)
  let reactor = MyReactor()
  let disposeBag = DisposeBag()

  // when
  scheduler
    .createHotObservable([
      .next(100, .refresh) // send .refresh at 100 scheduler time
    ])
    .subscribe(reactor.action)
    .disposed(by: disposeBag)

  // then
  let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {
    reactor.state.map(\.isLoading)
  }
  XCTAssertEqual(response.events.map(\.value.element), [
    false, // initial state
    true,  // just after .refresh
    false  // after refreshing
  ])
}

Scheduling

Define scheduler property to specify which scheduler is used for reducing and observing the state stream. Note that this queue must be a serial queue. The default scheduler is CurrentThreadScheduler.

final class MyReactor: Reactor {
  let scheduler: Scheduler = SerialDispatchQueueScheduler(qos: .default)

  func reduce(state: State, mutation: Mutation) -> State {
    // executed in a background thread
    heavyAndImportantCalculation()
    return state
  }
}

Examples

  • Counter: The most simple and basic example of ReactorKit
  • GitHub Search: A simple application which provides a GitHub repository search
  • RxTodo: iOS Todo Application using ReactorKit
  • Cleverbot: iOS Messaging Application using Cleverbot and ReactorKit
  • Drrrible: Dribbble for iOS using ReactorKit (App Store)
  • Passcode: Passcode for iOS RxSwift, ReactorKit and IGListKit example
  • Flickr Search: A simple application which provides a Flickr Photo search with RxSwift and ReactorKit
  • ReactorKitExample
  • reactorkit-keyboard-example: iOS Application example for develop keyboard-extensions using ReactorKit Architecture.
  • SWHub: Use ReactorKit develop the Github client

Dependencies

Requirements

  • Swift 5
  • iOS 8
  • macOS 10.11
  • tvOS 9.0
  • watchOS 2.0

Installation

ReactorKit officially supports CocoaPods only.

Podfile

pod 'ReactorKit'

ReactorKit does not officially support Carthage.

Cartfile

github "ReactorKit/ReactorKit"

Most Carthage installation issues can be resolved with the following:

carthage update 2>/dev/null
(cd Carthage/Checkouts/ReactorKit && swift package generate-xcodeproj)
carthage build

Contribution

Any discussions and pull requests are welcomed 💖

  • To development:

    $ TEST=1 swift package generate-xcodeproj
  • To test:

    $ swift test

Community

Join

Community Projects

Who's using ReactorKit


StyleShare Kakao Wantedly

DocTalk Constant Contact KT

Hyperconnect Toss LINE Pay

LINE Pay

Are you using ReactorKit? Please let me know!

Changelog

  • 2017-04-18
    • Change the repository name to ReactorKit.
  • 2017-03-17
    • Change the architecture name from RxMVVM to The Reactive Architecture.
    • Every ViewModels are renamed to ViewReactors.

License

ReactorKit is under MIT license. See the LICENSE for more info.

GitHub

https://github.com/ReactorKit/ReactorKit
Comments
  • 1. Carthage: no shared scheme

    Hi,

    currently it is not possible to install ReactorKit using Carthage.

    *** Skipped building ReactorKit due to the error:
    Dependency "ReactorKit" has no shared framework schemes for any of the platforms: iOS
    
    Reviewed by zdnk at 2017-05-19 11:30
  • 2. reactor.state subscribe onNext twice????

    func bind(reactor: HomeViewReactor) {
            // Action
            self.rx.viewDidLoad
                .map{ Reactor.Action.initializationTest }
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            // State
            
            reactor.state
                .map { $0.test }
                .debug()
                .filterNil()
                .subscribe(onNext: { (test) in
                    print("---------call it back twice-----------")
                    print(test)
                })
                .disposed(by: disposeBag)
    }
    

    I feel like crying when I meet this question。。。

    Reviewed by ripplek at 2018-09-03 09:34
  • 3. Add "Distinct" property wrapper

    Motivation

    Observer subscribing to the reactor state causes unnecessary view re-rendering when the distinctUntilChanged operator is not applied.

    class TestReactor: Reactor {
      
      struct State {
        var items: [String] = ["a", "b", "c"]
      }
    }
    
    class TestViewController: UIViewController, View {
    
      func bind(reactor: TestReactor) {
        reactor.state
          .map { $0.items }
          .distinctUntilChanged()
          .subscribe(onNext: { items in
            // 🤔 If distinctUntilchanged is not applied, there is a problem that duplicate data comes in.
            // And it is not compulsory and may be omitted by mistake.
          })
          .disposed(by: disposeBag)
      }
    }
    

    Therefore, in order to prevent unnecessary re-rendering, this problem must be prevented during the property declaration phase. This idea was inspired by Pulse. Thanks to @tokijh

    Feature

    Therefore, introduce a property wrapper called Distinct.

    Property wrapped in Distinct can be obtained through state method and keyPath. At this time, it is executed only when the value changes, and the above-mentioned problems can be solved. This method simplifies state subscriptions and is more intuitive.

    // before
    reactor.state.map { $0.value }.distinctUntilChanged()
    
    // after
    reactor.state(\.$value) 
    

    In order to apply Distinct property wrappers, the type of property must be hashable. Here's an improved code.

    class TestReactor: Reactor {
      
      struct State {
        @Distinct var items: [String] = ["a", "b", "c"]
      }
    }
    
    class TestViewController: UIViewController, View {
    
      func bind(reactor: TestReactor) {
        reactor.state(\.$items)
          .subscribe(onNext: { items in
            // ✅ Except for the first run, it only works when the value changes.
          })
          .disposed(by: disposeBag)
      }
    }
    
    Reviewed by haeseoklee at 2022-03-20 13:51
  • 4. installing via Carthage not working

    carthage update ReactorKit platform iOS result: *** Skipped building ReactorKit due to the error: Dependency "ReactorKit" has no shared framework schemes

    Reviewed by ichina at 2018-04-15 14:32
  • 5. GET/POST asynchronous

    I know that mutate is used for updating the state whenever actions happen, I am trying to make an API get request to receive an access token to make after that GET request, a POST request using that token in my authorization header in that post request, how do I make only one get request then the post request using that access token obtained from my first get request?

    Reviewed by karlzakhary at 2017-12-04 19:20
  • 6. Consider making reducer run before mutations.

    Given

    State

    struct State {
      var isLoading = false
      var tasks = [Task] 
    }
    

    Actions

    enum Action {
      case requestTasks
      case requestTasksCompleted([Task])
    }
    

    Dispatching .requestTasks action first runs the reducer and sets isLoading = true thus the UI can update, e.g show a spinner. Then in the mutation (what Redux-Observable[0] calls an "epic") you can start the request and on completion dispatch .requestTasksCompleted([Task]) for the reduce tor reload the list.

    As of now, you would need three actions, e.g .setLoading , where before every requestTasks manually first dispatch .setLoading.

    Edit: Could even use generics to ensure that the actions for Input is not the same as Output in the mutation so you don't get a infinite loop which they can't do with JS :-)

    0

    Reviewed by seivan at 2017-04-22 01:18
  • 7. Why is observing state on mainThread not supported in 2.1.0 release?

    Before 2.1.0 release, state was observed on mainThread, but now it is not certain. This causes crashes on places where I assumed state is observed from mainThread.

    I see scheduler property, but scheduler observes on action stream. So if thread changes at mutate()or transform(), it will override scheduler stream.

    Is it possible to rollback to the days when state was guaranteed to be delivered from mainThread? Or could there be a way to fix this problem? Thank you

    Reviewed by sanichdaniel at 2020-05-14 14:25
  • 8. UITableView.rx.contentOffset not work within func bind(...)

    import UIKit
    import RxSwift
    import protocol ReactorKit.View
    import RxCocoa
    
    class FirstViewController: UIViewController,View {
        var disposeBag: DisposeBag = DisposeBag()
        
        typealias Reactor = MyReactor
        
        var tbView = UITableView(frame: CGRect(x: 0.0, y: 80.0, width: 375.0, height: 300.0))
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(tbView)
            tbView.backgroundColor = UIColor.blue
    
            // Do any additional setup after loading the view.
        }
        func bind(reactor: FirstViewController.Reactor) {
            tbView.rx.contentOffset
                .subscribe(onNext: {point in
                    print(point)
                })
                .disposed(by: disposeBag)
        }
    }
    

    The tbView.rx.contentOffset is not work when I drag tbView。but tbView.rx.contendOffset have been work within func viewDidLoad().

    Reviewed by Hades-li at 2019-01-15 09:47
  • 9. Update Swift Package to be compatible with XCode 12

    While building ReactorKit with Carthage using XCode 12, the build fails with a bunch of error messages about iOS 8 being an unsupported deployment target. Increasing the deployment target to iOS 9 in the Swift Package definition file resolves the issue.

    warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99. (in target 'RxSwift' from project 'ReactorKit')
    warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99. (in target 'ReactorKitRuntime' from project 'ReactorKit')
    ** ARCHIVE FAILED **
    
    
    The following build commands failed:
    	CompileSwift normal armv7
    	CompileSwiftSources normal arm64 com.apple.xcode.tools.swift.compiler
    	CompileSwift normal arm64
    (3 failures)
    
    Reviewed by dodgecm at 2020-09-17 16:42
  • 10. Mutation base on async call result

    Hi , I found myself confused in handling muations in async call

    
     // action
        func mutate(action: Action) -> Observable<Mutation> {
            switch action {
                case let .requestSMSCode(phone):
                    return createRequestSMSCodeTask(phone)
               ....
    }
    
    func createRequestSMSCodeTask(_ phone: String) -> Observable<Mutation> {
        
            let startLoading = Observable<Mutation>.just(
                .updateNetworkStatus(.loading("sending code"))
            )
    
            let createErrorLoading: (_ error: Error) ->Observable<Mutation> = { error in
                return Observable<Mutation>.just(
                        .updateNetworkStatus(.failed(" sent code failed"))
                )
            }
    
            let startCoundown = Observable<Int>.interval(1, scheduler: MainScheduler.instance).take(60).map(Mutation.updateCountDownLeftSeconds)
    
            let requestCode = self.userService.requestSMSCode(phone).asObservable()
                .map {_ in Mutation.updateNetworkStatus(.loaded("code sent")) }
                .catchError(createErrorLoading)
            
            return .concat([startLoading, requestCode, startCoundown ])
        }
    

    whether to performstartCoundown task is based on requestCode task is success or failed, but startCoundown task execution has no context to know pervious status

    Reviewed by waltcow at 2018-05-17 08:58
  • 11. Triggering Action

    Is there any chance to trigger the Action without subscribing to State?

    For Example: I have Action saveKey(Key)

    enum Action {
      case saveKey(Key)
    }
    

    and I want to trigger action case without returning any Mutation case.

    func mutate(action: Action) -> Observable<Mutation> {
      switch action {
      case let .saveKey(key):
        userService.appendKey(key)
        return .empty()
      }
    }
    

    Should I return .empty() observable? But it doesn't work

    Reviewed by asyl at 2017-08-27 06:56
  • 12. Renamed public protocol 'View' to 'ReactorView'

    It was clashing with SwiftUI's public protocol named 'View'.

    SwiftUI's public protocol: https://developer.apple.com/documentation/swiftui/view

    Motivation:

    It was discovered that views on UIKit can be previewed on real time without building the project every time same as in projects which are built on SwiftUI.

    However, current version of ReactorKit has the same named public protocol for the UIViewControllers to inherit Reactor automatically. I renamed the protocol from 'View' to 'ReactorView', which should solve the issue.

    Reviewed by yermukhanbet at 2022-04-26 06:57
  • 13. View is ambiguous type. Clash between SwiftUI public protocol and ReactoriKit's public protocol.

    Our team is using ReactorKit, and we are loving it a lot! Currently we are working on UIKit. However, we are working right now on adding preview feature same as on SwiftUI's one.

    Following this tutorial: https://www.swiftjectivec.com/using-xcode-previews-for-uikit/

    However, as we figured out, there is a clash between public protocol 'View' from SwiftUI, and public protocol from ReactorKit 'View'

    Screen Shot 2022-04-26 at 3 44 22 PM Screen Shot 2022-04-26 at 3 44 50 PM

    Are there any workarounds this issue? Or may be any plans for renaming public protocol 'View' ?

    Reviewed by yermukhanbet at 2022-04-26 06:45
  • 14. one attribute bind another attribute in same reactor

    I have a state. There are two attributes apples and blueApples. When the apples changed, I want to trigger filter apples to set blueApples. How can I do it in same reactor ? I don't have any idea. Anyone can help?

    struct State {
         var apples: [Apple] = []
         var blueApples: [Apple] = []
    }
    
    
    Reviewed by kaich at 2021-12-23 03:35
  • 15. Add state(_:) to use KeyPath 🔑

    Add state(_:) using KeyPath.
    Inspired by #176 tokijh's Pulse implementation.

    It can write a little less code using KeyPath.

    Reactor

    final class FooReactor: Reactor {
      enum Action {
        case increase
      }
    
      enum Mutation {
        case increaseValue
      }
    
      struct State {
        var value: Int
      }
    
      func mutate(action: Action) -> Observable<Mutation> {
        switch action {
          case .increase:
            return Observable.just(Mutation.increaseValue)
        }
      }
    
      func reduce(state: State, mutation: Mutation) -> State {
        var newState = state
        switch mutation {
        case .increaseValue:
          newState.value += 1
        }
    
        return newState
      }
    }
    

    View

    Before

    reactor.state.map(\.value)
      .subscribe(onNext: value in
        print(value)
      })
      .disposed(by: disposeBag)
    
    reactor.action.onNext(.increase) // print '1'
    reactor.action.onNext(.increase) // print '2'
    

    After

    reactor.state(\.value)
      .subscribe(onNext: value in
        print(value)
      })
      .disposed(by: disposeBag)
    
    reactor.action.onNext(.increase) // print '1'
    reactor.action.onNext(.increase) // print '2'
    

    // Pulse
    reactor.pulse(\.$alertMessage)
      .compactMap { $0 }
      .subscribe(onNext: { [weak self] (message: String) in
        self?.showAlert(message)
      })
      .disposed(by: disposeBag)
    
    // use KeyPath state(_:) 
    reactor.state(\.value)
      .subscribe(onNext: value in
        print(value)
      })
      .disposed(by: disposeBag)
    

    With #176 Pulse,
    it becomes a more consistent code!

    Reviewed by jsryudev at 2021-07-14 03:11
  • 16. Is there a better way to generate sections?

    I have these state, when the isStarred/user/models changed will update the sections. image So, I generate sections in reduce method. image Is there a better way to generate sections?

    Reviewed by tospery at 2021-06-18 06:34
ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.
ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. ReSwift helps you to separate three important concerns of your app's components.

Jun 20, 2022
A New, Modern Reactive State Management Library for Swift and SwiftUI (The iOS implementation of Recoil)
A New, Modern Reactive State Management Library for Swift and SwiftUI (The iOS implementation of Recoil)

RecoilSwift RecoilSwift is a lightweight & reactive swift state management library. RecoilSwift is a SwiftUI implementation of recoil.js which powered

May 22, 2022
Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift.
 Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift.

ReactiveSwift offers composable, declarative and flexible primitives that are built around the grand concept of streams of values over time. These primitives can be used to uniformly represent common Cocoa and generic programming patterns that are fundamentally an act of observation.

Jun 21, 2022
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.
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

Jan 29, 2022
RxXRepository: reactive extension for XRepository

RxXRepository is reactive extension for XRepository. This extension allows imple

Jan 6, 2022
EventBroadcaster is a lightweight event handler framework, written in swift for iOS, macOS, tvOS & watchOS applications.
EventBroadcaster is a lightweight event handler framework, written in swift for iOS, macOS, tvOS & watchOS applications.

EventBroadcaster is a lightweight event handler framework, written in swift for iOS, macOS, tvOS & watchOS applications.

Sep 29, 2021
Predictable state management for SwiftUI applications.
Predictable state management for SwiftUI applications.

SwiftDux Predictable state management for SwiftUI applications. SwiftDux is a state container inspired by Redux and built on top of Combine and SwiftU

May 13, 2022
A Swift based Future/Promises Library for IOS and OS X.

FutureKit for Swift A Swift based Future/Promises Library for IOS and OS X. Note - The latest FutureKit is works 3.0 For Swift 2.x compatibility use v

May 11, 2022
A micro-library for creating and observing events.

Signals Signals is a library for creating and observing events. It replaces delegates, actions and NSNotificationCenter with something much more power

May 27, 2022
A simple and predictable state management library inspired by Flux + Elm + Redux.

A simple and predictable state management library inspired by Flux + Elm + Redux. Flywheel is built on top of Corotuines using the concepts of structured concurrency. At the core, lies the State Machine which is based on actor model.

Jun 2, 2022
Modern thread-safe and type-safe key-value observing for Swift and Objective-C

Now Archived and Forked PMKVObserver will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork o

Mar 13, 2022
Define and chain Closures with Inputs and Outputs

Closure Define and chain Closures with Inputs and Outputs Examples No Scoped State let noStateCount = Closure<String, String> { text in String(repea

May 18, 2022
Write great asynchronous code in Swift using futures and promises

BrightFutures How do you leverage the power of Swift to write great asynchronous code? BrightFutures is our answer. BrightFutures implements proven fu

Jun 18, 2022
Tempura - A holistic approach to iOS development, inspired by Redux and MVVM
Tempura - A holistic approach to iOS development, inspired by Redux and MVVM

Tempura is a holistic approach to iOS development, it borrows concepts from Redux (through Katana) and MVVM. ?? Installation Requirements CocoaPods ??

Jun 21, 2022
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms

SwiftUI-compatible framework for building browser apps with WebAssembly At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

Jun 19, 2022
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms

SwiftUI-compatible framework for building browser apps with WebAssembly At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

Jun 24, 2022
Very simple Observable and Publisher implementation for iOS apps.

Very simple Observable and Publisher implementation for iOS apps.

Jun 11, 2022
Interactive notification pop-over (aka "Toast) modeled after the iOS AirPods and Apple Pencil indicator.
Interactive notification pop-over (aka

Interactive notification pop-over (aka "Toast) modeled after the iOS AirPods and Apple Pencil indicator. Installation The recommended way is to use Co

Apr 1, 2022
Bond is a Swift binding framework that takes binding concepts to a whole new level.

Bond, Swift Bond Update: Bond 7 has been released! Check out the migration guide to learn more about the update. Bond is a Swift binding framework tha

Jun 16, 2022