Convert Figma logo to code with AI

ReSwift logoReSwift

Unidirectional Data Flow in Swift - Inspired by Redux

7,565
519
7,565
50

Top Related Projects

24,441

Reactive Programming in Swift

Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

4,232

A Swift binding framework

15,190

Network abstraction layer written in Swift.

A library for reactive and unidirectional Swift applications

Quick Overview

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. It helps manage the state of iOS applications in a predictable way, making it easier to develop and maintain complex apps by enforcing a clear separation between state, views, and actions.

Pros

  • Promotes a clear and predictable state management architecture
  • Simplifies debugging and testing by centralizing application state
  • Encourages modular and reusable code
  • Integrates well with SwiftUI and UIKit

Cons

  • Steep learning curve for developers unfamiliar with Redux concepts
  • Can be overkill for simple applications
  • Requires additional boilerplate code compared to traditional MVC architecture
  • May introduce performance overhead for very large state trees

Code Examples

  1. Creating a basic store:
import ReSwift

struct AppState: StateType {
    var counter: Int = 0
}

let store = Store<AppState>(reducer: counterReducer, state: nil)
  1. Defining a reducer:
func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    switch action {
    case _ as IncrementAction:
        state.counter += 1
    case _ as DecrementAction:
        state.counter -= 1
    default:
        break
    }
    return state
}
  1. Dispatching actions:
store.dispatch(IncrementAction())
  1. Subscribing to state changes:
class CounterViewController: UIViewController, StoreSubscriber {
    @IBOutlet weak var counterLabel: UILabel!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        store.subscribe(self)
    }
    
    func newState(state: AppState) {
        counterLabel.text = "\(state.counter)"
    }
}

Getting Started

  1. Install ReSwift using Swift Package Manager by adding the following to your Package.swift:
dependencies: [
    .package(url: "https://github.com/ReSwift/ReSwift.git", from: "6.1.0")
]
  1. Import ReSwift in your Swift files:
import ReSwift
  1. Define your app state, create actions, implement reducers, and set up your store as shown in the code examples above.

  2. Subscribe to state changes in your view controllers or SwiftUI views, and dispatch actions to modify the state.

Competitor Comparisons

24,441

Reactive Programming in Swift

Pros of RxSwift

  • More comprehensive and flexible for complex asynchronous operations
  • Extensive set of operators for transforming and combining data streams
  • Strong community support and ecosystem of extensions

Cons of RxSwift

  • Steeper learning curve due to its complexity and vast API surface
  • Can lead to overuse of reactive paradigms, potentially complicating simple tasks
  • Higher memory footprint and potential performance overhead for simple use cases

Code Comparison

ReSwift:

struct AppState {
    var counter: Int = 0
}

struct IncrementAction: Action {}

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    switch action {
    case _ as IncrementAction:
        state.counter += 1
    default:
        break
    }
    return state
}

RxSwift:

let counter = BehaviorSubject<Int>(value: 0)

counter
    .map { $0 + 1 }
    .bind(to: counter)
    .disposed(by: disposeBag)

counter.onNext(1) // Increment counter

Both ReSwift and RxSwift offer state management solutions, but RxSwift provides a more comprehensive toolkit for reactive programming. ReSwift focuses on unidirectional data flow and predictable state mutations, while RxSwift excels in handling complex asynchronous operations and data streams. The choice between them depends on the specific needs of your project and your team's familiarity with reactive programming concepts.

Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

Pros of ReactiveCocoa

  • More comprehensive framework for reactive programming
  • Supports a wider range of reactive patterns and operators
  • Better integration with Apple's Combine framework

Cons of ReactiveCocoa

  • Steeper learning curve due to its complexity
  • Can lead to more verbose code in simple use cases
  • Potential performance overhead for complex reactive chains

Code Comparison

ReactiveCocoa:

let searchResults = searchTextField.reactive.continuousTextValues
    .throttle(0.3, on: QueueScheduler.main)
    .flatMap(.latest) { (query: String) -> SignalProducer<[SearchResult], Never> in
        return API.search(query)
    }

ReSwift:

struct SearchAction: Action {
    let query: String
}

func searchReducer(action: Action, state: SearchState?) -> SearchState {
    var state = state ?? SearchState()
    switch action {
    case let searchAction as SearchAction:
        state.results = API.search(searchAction.query)
    }
    return state
}

ReactiveCocoa provides a more declarative approach to handling asynchronous operations, while ReSwift focuses on unidirectional data flow and state management. ReactiveCocoa's code is more concise for complex async operations, but ReSwift's approach can be simpler for state management in larger applications.

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

Pros of swift-composable-architecture

  • Offers a more comprehensive approach to state management and UI composition
  • Provides built-in testing utilities for easier and more thorough testing
  • Supports SwiftUI out of the box with better integration

Cons of swift-composable-architecture

  • Steeper learning curve due to its more complex architecture
  • Requires more boilerplate code compared to ReSwift
  • May be overkill for smaller projects or simpler use cases

Code Comparison

ReSwift:

struct AppState {
    var counter: Int = 0
}

struct IncrementAction: Action {}

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    switch action {
    case _ as IncrementAction:
        state.counter += 1
    default:
        break
    }
    return state
}

swift-composable-architecture:

struct CounterState: Equatable {
    var count: Int = 0
}

enum CounterAction: Equatable {
    case increment
}

let counterReducer = Reducer<CounterState, CounterAction, Void> { state, action, _ in
    switch action {
    case .increment:
        state.count += 1
        return .none
    }
}
4,232

A Swift binding framework

Pros of Bond

  • More flexible and lightweight, allowing for reactive programming without enforcing a strict architecture
  • Provides powerful binding capabilities for UI components, simplifying view updates
  • Supports a wider range of reactive programming patterns beyond just state management

Cons of Bond

  • Less structured approach to state management compared to ReSwift's unidirectional data flow
  • Steeper learning curve for developers new to reactive programming concepts
  • May lead to more complex and harder-to-maintain code in large applications

Code Comparison

Bond example:

let name = Observable<String?>("")
let nameLabel = UILabel()
name.bind(to: nameLabel.reactive.text)

ReSwift example:

struct AppState: StateType {
    var name: String = ""
}
store.subscribe { state in
    nameLabel.text = state.name
}

Bond offers a more direct binding approach, while ReSwift follows a more structured state management pattern. Bond's reactive nature allows for easier UI updates, but ReSwift's unidirectional data flow can lead to more predictable and maintainable state changes in complex applications.

15,190

Network abstraction layer written in Swift.

Pros of Moya

  • Simplifies network requests and API integration
  • Provides type-safe network layer with strong abstraction
  • Supports testing and stubbing of network calls

Cons of Moya

  • Learning curve for complex API structures
  • May be overkill for simple network requests
  • Requires additional setup compared to basic URLSession

Code Comparison

Moya:

let provider = MoyaProvider<MyAPI>()
provider.request(.userProfile) { result in
    switch result {
    case let .success(response):
        let data = response.data
        // Handle the response
    case let .failure(error):
        // Handle the error
    }
}

ReSwift:

store.dispatch(FetchUserProfileAction())

func handleState(_ state: AppState) {
    let userProfile = state.userProfile
    // Update UI with user profile data
}

Key Differences

  • Moya focuses on network abstraction, while ReSwift is a state management framework
  • Moya simplifies API calls, whereas ReSwift manages application state
  • Moya is specific to networking, while ReSwift can be used for various state management scenarios

Use Cases

  • Choose Moya for projects with complex API integrations
  • Opt for ReSwift when you need a robust state management solution
  • Consider using both in conjunction for a well-structured app with clean networking and state management

A library for reactive and unidirectional Swift applications

Pros of ReactorKit

  • More lightweight and flexible architecture
  • Better suited for reactive programming with RxSwift integration
  • Easier to implement and understand for developers familiar with reactive concepts

Cons of ReactorKit

  • Steeper learning curve for developers new to reactive programming
  • Less structured than ReSwift's strict unidirectional data flow
  • Potentially more complex state management in large applications

Code Comparison

ReactorKit:

struct Reactor: ReactorKit.Reactor {
    enum Action { case increment, decrement }
    enum Mutation { case increaseValue, decreaseValue }
    struct State { var value: Int = 0 }

    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .increment: return .just(.increaseValue)
        case .decrement: return .just(.decreaseValue)
        }
    }
}

ReSwift:

struct CounterState: StateType {
    var count: Int = 0
}

struct CounterAction: Action {
    let amount: Int
}

func counterReducer(action: Action, state: CounterState?) -> CounterState {
    var state = state ?? CounterState()
    switch action {
    case let action as CounterAction: state.count += action.amount
    default: break
    }
    return state
}

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

ReSwift

Build Status Code coverage status CocoaPods Compatible Platform support License MIT Reviewed by Hound

Introduction

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:

  • State: in a ReSwift app the entire app state is explicitly stored in a data structure. This helps avoid complicated state management code, enables better debugging and has many, many more benefits...
  • Views: in a ReSwift app your views update when your state changes. Your views become simple visualizations of the current app state.
  • State Changes: in a ReSwift app you can only perform state changes through actions. Actions are small pieces of data that describe a state change. By drastically limiting the way state can be mutated, your app becomes easier to understand and it gets easier to work with many collaborators.

The ReSwift library is tiny - allowing users to dive into the code, understand every single line and hopefully contribute.

ReSwift is quickly growing beyond the core library, providing experimental extensions for routing and time traveling through past app states!

Excited? So are we 🎉

Check out our public gitter chat!

Table of Contents

About ReSwift

ReSwift relies on a few principles:

  • The Store stores your entire app state in the form of a single data structure. This state can only be modified by dispatching Actions to the store. Whenever the state in the store changes, the store will notify all observers.
  • Actions are a declarative way of describing a state change. Actions don't contain any code, they are consumed by the store and forwarded to reducers. Reducers will handle the actions by implementing a different state change for each action.
  • Reducers provide pure functions, that based on the current action and the current app state, create a new app state

For a very simple app, that maintains a counter that can be increased and decreased, you can define the app state as following:

struct AppState {
    var counter: Int = 0
}

You would also define two actions, one for increasing and one for decreasing the counter. In the Getting Started Guide you can find out how to construct complex actions. For the simple actions in this example we can define empty structs that conform to action:

struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}

Your reducer needs to respond to these different action types, that can be done by switching over the type of action:

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()

    switch action {
    case _ as CounterActionIncrease:
        state.counter += 1
    case _ as CounterActionDecrease:
        state.counter -= 1
    default:
        break
    }

    return state
}

In order to have a predictable app state, it is important that the reducer is always free of side effects, it receives the current app state and an action and returns the new app state.

To maintain our state and delegate the actions to the reducers, we need a store. Let's call it mainStore and define it as a global constant, for example in the app delegate file:

let mainStore = Store<AppState>(
	reducer: counterReducer,
	state: nil
)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
	[...]
}

Lastly, your view layer, in this case a view controller, needs to tie into this system by subscribing to store updates and emitting actions whenever the app state needs to be changed:

class CounterViewController: UIViewController, StoreSubscriber {

    @IBOutlet var counterLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        mainStore.subscribe(self)
    }

    override func viewWillDisappear(_ animated: Bool) {
        mainStore.unsubscribe(self)
    }

    func newState(state: AppState) {
        counterLabel.text = "\(state.counter)"
    }

    @IBAction func increaseButtonTapped(_ sender: UIButton) {
        mainStore.dispatch(
            CounterActionIncrease()
        )
    }

    @IBAction func decreaseButtonTapped(_ sender: UIButton) {
        mainStore.dispatch(
            CounterActionDecrease()
        )
    }

}

The newState method will be called by the Store whenever a new app state is available, this is where we need to adjust our view to reflect the latest app state.

Button taps result in dispatched actions that will be handled by the store and its reducers, resulting in a new app state.

This is a very basic example that only shows a subset of ReSwift's features, read the Getting Started Guide to see how you can build entire apps with this architecture. For a complete implementation of this example see the CounterExample project.

Create a subscription of several substates combined

Just create a struct representing the data model needed in the subscriber class, with a constructor that takes the whole app state as a param. Consider this constructor as a mapper/selector from the app state to the subscriber state. Being MySubState a struct and conforming to Equatable, ReSwift (by default) will not notify the subscriber if the computed output hasn't changed. Also, Swift will be able to infer the type of the subscription.

struct MySubState: Equatable {
    // Combined substate derived from the app state.
    
    init(state: AppState) {
        // Compute here the substate needed.
    }
}
store.subscribe(self) { $0.select(MySubState.init) }
    
func newState(state: MySubState) {
    // Profit!
}

Why ReSwift?

Model-View-Controller (MVC) is not a holistic application architecture. Typical Cocoa apps defer a lot of complexity to controllers since MVC doesn't offer other solutions for state management, one of the most complex issues in app development.

Apps built upon MVC often end up with a lot of complexity around state management and propagation. We need to use callbacks, delegations, Key-Value-Observation and notifications to pass information around in our apps and to ensure that all the relevant views have the latest state.

This approach involves a lot of manual steps and is thus error prone and doesn't scale well in complex code bases.

It also leads to code that is difficult to understand at a glance, since dependencies can be hidden deep inside of view controllers. Lastly, you mostly end up with inconsistent code, where each developer uses the state propagation procedure they personally prefer. You can circumvent this issue by style guides and code reviews but you cannot automatically verify the adherence to these guidelines.

ReSwift attempts to solve these problem by placing strong constraints on the way applications can be written. This reduces the room for programmer error and leads to applications that can be easily understood - by inspecting the application state data structure, the actions and the reducers.

This architecture provides further benefits beyond improving your code base:

  • Stores, Reducers, Actions and extensions such as ReSwift Router are entirely platform independent - you can easily use the same business logic and share it between apps for multiple platforms (iOS, tvOS, etc.)
  • Want to collaborate with a co-worker on fixing an app crash? Use ReSwift Recorder to record the actions that lead up to the crash and send them the JSON file so that they can replay the actions and reproduce the issue right away.
  • Maybe recorded actions can be used to build UI and integration tests?

The ReSwift tooling is still in a very early stage, but aforementioned prospects excite me and hopefully others in the community as well!

You can also watch this talk on the motivation behind ReSwift.

Getting Started Guide

A Getting Started Guide that describes the core components of apps built with ReSwift lives here.

To get an understanding of the core principles we recommend reading the brilliant redux documentation.

Installation

CocoaPods

You can install ReSwift via CocoaPods by adding it to your Podfile:

use_frameworks!

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

pod 'ReSwift'

And run pod install.

Carthage

You can install ReSwift via Carthage by adding the following line to your Cartfile:

github "ReSwift/ReSwift"

Accio

You can install ReSwift via Accio by adding the following line to your Package.swift:

.package(url: "https://github.com/ReSwift/ReSwift.git", .upToNextMajor(from: "6.0.0")),

Next, add ReSwift to your App targets dependencies like so:

.target(
    name: "App",
    dependencies: [
        "ReSwift",
    ]
),

Then run accio update.

Swift Package Manager

You can install ReSwift via Swift Package Manager by adding the following line to your Package.swift:

import PackageDescription

let package = Package(
    [...]
    dependencies: [
        .package(url: "https://github.com/ReSwift/ReSwift.git", from: "6.0.0"),
    ]
)

Checking out Source Code

After checking out the project run pod install to get the latest supported version of SwiftLint, which we use to ensure a consistent style in the codebase.

Demo

Using this library you can implement apps that have an explicit, reproducible state, allowing you, among many other things, to replay and rewind the app state, as shown below:

Extensions

This repository contains the core component for ReSwift, the following extensions are available:

  • ReSwift-Thunk: Provides a ReSwift middleware that lets you dispatch thunks (action creators) to encapsulate processes like API callbacks.
  • ReSwift-Router: Provides a ReSwift compatible Router that allows declarative routing in iOS applications
  • ReSwift-Recorder: Provides a Store implementation that records all Actions and allows for hot-reloading and time travel

Example Projects

  • CounterExample: A very simple counter app implemented with ReSwift.
  • CounterExample-Navigation-TimeTravel: This example builds on the simple CounterExample app, adding time travel with ReSwiftRecorder and routing with ReSwiftRouter.
  • GitHubBrowserExample: A real world example, involving authentication, network requests and navigation. Still WIP but should be the best resource for starting to adapt ReSwift in your own app.
  • ReduxMovieDB: A simple App that queries the tmdb.org API to display the latest movies. Allows searching and viewing details.
  • Meet: A real world application being built with ReSwift - currently still very early on. It is not up to date with the latest version of ReSwift, but is the best project for demonstrating time travel.
  • Redux-Twitter: A basic Twitter search implementation built with ReSwift and RxSwift, involing Twitter authentication, network requests and navigation.
  • CounterExample-SwiftUI: CounterExample using SwiftUI.

Production Apps with Open Source Code

Contributing

There's still a lot of work to do here! We would love to see you involved! You can find all the details on how to get started in the Contributing Guide.

Credits

  • Thanks a lot to Dan Abramov for building Redux - all ideas in here and many implementation details were provided by his library.

Get in touch

If you have any questions, you can find the core team on twitter:

We also have a public gitter chat!