A library for reactive and unidirectional Swift applications

Overview

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.

Comments
  • Carthage: no shared scheme

    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
    
    opened by zdnk 29
  • reactor.state   subscribe  onNext twice????

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

    opened by ripplek 10
  • Add

    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)
      }
    }
    
    opened by haeseoklee 7
  • installing via Carthage not working

    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

    opened by ichina 7
  • GET/POST asynchronous

    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?

    opened by karlzakhary 7
  • Consider making reducer run before mutations.

    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

    opened by seivan 7
  • Why is observing state on mainThread not supported in 2.1.0 release?

    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

    opened by sanichdaniel 6
  • UITableView.rx.contentOffset not work within func bind(...)

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

    opened by Hades-li 6
  • Add state(_:) to use KeyPath 🔑

    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!

    no-pr-activity 
    opened by jsryudev 5
  • Update Swift Package to be compatible with XCode 12

    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)
    
    opened by dodgecm 5
  • Migrate to RxRelay

    Migrate to RxRelay

    This PR is blocked by https://github.com/devxoul/RxExpect/pull/16 This PR closes https://github.com/ReactorKit/ReactorKit/issues/103 This PR closes https://github.com/ReactorKit/ReactorKit/issues/62

    no-pr-activity 
    opened by MortyMerr 5
  • View is ambiguous type. Clash between SwiftUI public protocol and ReactoriKit's public protocol.

    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' ?

    opened by yermukhanbet 3
  • one attribute bind another attribute in same reactor

    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] = []
    }
    
    
    opened by kaich 1
  • Is there a better way to generate sections?

    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?

    opened by tospery 0
  • Freezing occurs when running iOS app in MacBook (M1 process)

    Freezing occurs when running iOS app in MacBook (M1 process)

    I was experiencing freezing when running the iOS app on the MacBook (M1 process). So, looking for the cause, there was a problem with swizzling.

    https://github.com/ReactorKit/ReactorKit/blob/master/Sources/ReactorKitRuntime/ReactorKitRuntime.m

    #if !TARGET_OS_MACCATALYST
        [self swizzleInitializeOfClassNamed:@"NSViewController"];
    #endif
    

    It seems that only the iPad app is supported, but it seems that the iOS app must be handled together for normal operation.

    https://stackoverflow.com/questions/65042788/how-to-detect-if-an-ios-app-is-running-on-an-m1-mac

    So I'm checking the isiOSAppOnMac flag in the private repo and handling it, and I'm curious if there are any other ideas. (I am not good at English. Even if the words are awkward, I hope you understand.)

    opened by lcg5450 1
  • WeakMapTable

    WeakMapTable

    this PR break Carthage build https://github.com/ReactorKit/ReactorKit/pull/138

    discussed https://github.com/ReactorKit/ReactorKit/issues/166 https://github.com/ReactorKit/ReactorKit/issues/167

    opened by PierreBrisorgueil 0
Releases(3.2.0)
Owner
ReactorKit
A framework for reactive and unidirectional Swift application architecture
ReactorKit
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.

null 7.3k Jan 9, 2023
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

Holly Li 160 Dec 25, 2022
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.

null 20k Jan 3, 2023
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
RxXRepository: reactive extension for XRepository

RxXRepository is reactive extension for XRepository. This extension allows imple

Sashko Potapov 1 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.

Ali Samaiee 4 Oct 5, 2022
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

Steven Lambion 148 Jul 4, 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

null 759 Dec 2, 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

Tuomas Artman 454 Dec 21, 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.

Abhi Muktheeswarar 35 Dec 29, 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

Postmates Inc. 708 Jun 29, 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

Zach Eriksen 3 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

Thomas Visser 1.9k Dec 20, 2022
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 ??

Bending Spoons 692 Dec 17, 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 At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

TokamakUI 2k Dec 30, 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 At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

TokamakUI 2k Dec 29, 2022
Very simple Observable and Publisher implementation for iOS apps.

Very simple Observable and Publisher implementation for iOS apps.

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

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

Philip Kluz 108 Nov 11, 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

Declarative Hub 4.2k Jan 5, 2023