swift-composable-architecture
A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
Top Related Projects
Reactive Programming in Swift
Unidirectional Data Flow in Swift - Inspired by Redux
A Swift binding framework
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.
Network abstraction layer written in Swift.
Dependency injection framework for Swift with iOS/macOS/Linux
Quick Overview
The Composable Architecture (TCA) is a library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. It provides a way to build large, complex applications by composing smaller features together, making it easier to manage state, side effects, and dependencies.
Pros
- Promotes a modular and composable approach to app architecture
- Excellent testing support with easy-to-write, deterministic tests
- Comprehensive documentation and examples
- Strong type safety and compile-time checks
Cons
- Steep learning curve for developers new to functional programming concepts
- Can be verbose for simple use cases
- Potential performance overhead for very large state trees
- May require significant refactoring of existing codebases
Code Examples
- Defining a feature's state and actions:
struct Counter: ReducerProtocol {
struct State: Equatable {
var count = 0
}
enum Action: Equatable {
case incrementButtonTapped
case decrementButtonTapped
}
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .incrementButtonTapped:
state.count += 1
return .none
case .decrementButtonTapped:
state.count -= 1
return .none
}
}
}
- Creating a store and using it in a SwiftUI view:
struct CounterView: View {
let store: StoreOf<Counter>
var body: some View {
WithViewStore(self.store) { viewStore in
HStack {
Button("-") { viewStore.send(.decrementButtonTapped) }
Text("\(viewStore.count)")
Button("+") { viewStore.send(.incrementButtonTapped) }
}
}
}
}
- Composing features:
struct AppFeature: ReducerProtocol {
struct State: Equatable {
var counter: Counter.State
var profile: Profile.State
}
enum Action: Equatable {
case counter(Counter.Action)
case profile(Profile.Action)
}
var body: some ReducerProtocol<State, Action> {
Scope(state: \.counter, action: /Action.counter) {
Counter()
}
Scope(state: \.profile, action: /Action.profile) {
Profile()
}
}
}
Getting Started
-
Add the package to your Xcode project:
dependencies: [ .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.52.0") ]
-
Import the library in your Swift files:
import ComposableArchitecture
-
Define your first feature using the
ReducerProtocol
:struct MyFeature: ReducerProtocol { struct State: Equatable { /* ... */ } enum Action: Equatable { /* ... */ } func reduce(into state: inout State, action: Action) -> EffectTask<Action> { // Implement your feature's logic here } }
-
Create a store and use it in your SwiftUI views:
let store = Store(initialState: MyFeature.State(), reducer: MyFeature()) MyFeatureView(store: store)
Competitor Comparisons
Reactive Programming in Swift
Pros of RxSwift
- Mature and widely adopted in the iOS community
- Extensive documentation and community support
- Powerful operators for complex asynchronous operations
Cons of RxSwift
- Steeper learning curve for developers new to reactive programming
- Can lead to overuse of reactive patterns, potentially complicating simple tasks
- Memory management requires careful attention to avoid retain cycles
Code Comparison
RxSwift:
Observable.combineLatest(nameObservable, ageObservable)
.map { name, age in "\(name) is \(age) years old" }
.bind(to: resultLabel.rx.text)
.disposed(by: disposeBag)
Swift Composable Architecture:
struct AppState {
var name: String
var age: Int
}
let reducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, _ in
switch action {
case .setName(let name):
state.name = name
return .none
case .setAge(let age):
state.age = age
return .none
}
}
The Swift Composable Architecture focuses on a more structured approach to state management and side effects, while RxSwift provides a reactive programming paradigm for handling asynchronous events and data streams. The choice between the two depends on the specific needs of the project and the team's familiarity with each approach.
Unidirectional Data Flow in Swift - Inspired by Redux
Pros of ReSwift
- Simpler and more lightweight implementation of Redux architecture
- Easier to learn and implement for developers familiar with Redux
- More flexible and less opinionated, allowing for easier customization
Cons of ReSwift
- Less comprehensive feature set compared to Composable Architecture
- Lacks built-in support for side effects and asynchronous operations
- May require more boilerplate code for complex state management scenarios
Code Comparison
ReSwift:
struct AppState { var counter = 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
}
Composable Architecture:
struct CounterState: Equatable { var count = 0 }
enum CounterAction { case increment }
let counterReducer = Reducer<CounterState, CounterAction, Void> { state, action, _ in
switch action {
case .increment:
state.count += 1
return .none
}
}
The Composable Architecture provides a more structured approach with explicit state, action, and environment types, while ReSwift offers a simpler implementation that may require less initial setup for basic use cases.
A Swift binding framework
Pros of Bond
- Simpler learning curve and easier to adopt for smaller projects
- More flexible and lightweight, allowing for easier integration with existing codebases
- Focuses on reactive programming and data binding, which can be beneficial for UI-centric applications
Cons of Bond
- Less comprehensive state management compared to Swift Composable Architecture (SCA)
- May require additional libraries or custom solutions for complex app architectures
- Lacks built-in testing utilities and debugging tools that SCA provides
Code Comparison
Bond example:
let name = Observable<String?>("")
let greeting = name.map { "Hello, \($0 ?? "")!" }
greeting.bind(to: label.reactive.text)
Swift Composable Architecture example:
struct State { var name: String = "" }
enum Action { case nameChanged(String) }
let reducer = Reducer<State, Action, Void> { state, action, _ in
switch action {
case let .nameChanged(newName):
state.name = newName
return .none
}
}
Both examples demonstrate basic state management and data flow, but SCA provides a more structured approach with explicit state and actions, while Bond focuses on reactive bindings.
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.
Pros of ReactiveCocoa
- Mature and battle-tested framework with a large community
- Supports multiple programming paradigms (functional, reactive, imperative)
- Extensive documentation and learning resources available
Cons of ReactiveCocoa
- Steeper learning curve, especially for developers new to reactive programming
- Can lead to complex code if not used carefully
- Less opinionated about application architecture compared to TCA
Code Comparison
ReactiveCocoa:
let searchResults = searchText
.throttle(0.3, on: QueueScheduler.main)
.flatMap(.latest) { text in
return API.search(text)
}
Swift Composable Architecture:
struct Search: Reducer {
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .searchTextChanged(let text):
return .run { send in
try await Task.sleep(for: .milliseconds(300))
let results = await API.search(text)
await send(.searchResultsLoaded(results))
}
}
}
}
Both frameworks offer powerful tools for managing application state and side effects, but they approach the problem differently. ReactiveCocoa focuses on reactive streams and signal processing, while Swift Composable Architecture provides a more structured, state-driven approach to application design.
Network abstraction layer written in Swift.
Pros of Moya
- Simpler and more lightweight, focusing specifically on network abstraction
- Easier to integrate into existing projects without major architectural changes
- Provides a clear separation between network requests and response handling
Cons of Moya
- Less comprehensive in terms of overall application architecture
- Doesn't provide built-in state management or side effect handling
- May require additional libraries for more complex app structures
Code Comparison
Moya example:
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
}
}
Swift Composable Architecture example:
struct AppState {
var userProfile: UserProfile?
}
let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, environment in
switch action {
case .fetchUserProfile:
return environment.apiClient.fetchUserProfile()
.receive(on: environment.mainQueue)
.catchToEffect(AppAction.userProfileResponse)
}
}
While Moya focuses on network abstraction, Swift Composable Architecture provides a more comprehensive approach to app architecture, including state management and side effect handling. The choice between the two depends on the specific needs of your project and the desired level of architectural complexity.
Dependency injection framework for Swift with iOS/macOS/Linux
Pros of Swinject
- Lightweight and easy to integrate into existing projects
- Supports both Swift and Objective-C
- Extensive documentation and community support
Cons of Swinject
- Limited to dependency injection, not a full architecture solution
- May require additional setup for complex scenarios
- Less opinionated, which can lead to inconsistent usage across a project
Code Comparison
Swinject:
let container = Container()
container.register(Animal.self) { _ in Cat(name: "Mimi") }
container.register(Person.self) { r in
PetOwner(pet: r.resolve(Animal.self)!)
}
Swift Composable Architecture:
struct AppState: Equatable {
var counter: Int = 0
}
enum AppAction: Equatable {
case incrementCounter
}
let appReducer = Reducer<AppState, AppAction, Void> { state, action, _ in
switch action {
case .incrementCounter:
state.counter += 1
return .none
}
}
While Swinject focuses on dependency injection, Swift Composable Architecture provides a more comprehensive approach to state management and UI composition. Swinject is ideal for projects that need a flexible DI solution, while Swift Composable Architecture is better suited for building complex, state-driven applications with a consistent architecture throughout.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
The Composable Architecture
The Composable Architecture (TCA, for short) is a library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. It can be used in SwiftUI, UIKit, and more, and on any Apple platform (iOS, macOS, visionOS, tvOS, and watchOS).
- What is the Composable Architecture?
- Learn more
- Examples
- Basic usage
- Documentation
- Community
- Installation
- Translations
What is the Composable Architecture?
This library provides a few core tools that can be used to build applications of varying purpose and complexity. It provides compelling stories that you can follow to solve many problems you encounter day-to-day when building applications, such as:
-
State management
How to manage the state of your application using simple value types, and share state across many screens so that mutations in one screen can be immediately observed in another screen. -
Composition
How to break down large features into smaller components that can be extracted to their own, isolated modules and be easily glued back together to form the feature. -
Side effects
How to let certain parts of the application talk to the outside world in the most testable and understandable way possible. -
Testing
How to not only test a feature built in the architecture, but also write integration tests for features that have been composed of many parts, and write end-to-end tests to understand how side effects influence your application. This allows you to make strong guarantees that your business logic is running in the way you expect. -
Ergonomics
How to accomplish all of the above in a simple API with as few concepts and moving parts as possible.
Learn More
The Composable Architecture was designed over the course of many episodes on Point-Free, a video series exploring functional programming and the Swift language, hosted by Brandon Williams and Stephen Celis.
You can watch all of the episodes here, as well as a dedicated, multipart tour of the architecture from scratch.
Examples
This repo comes with lots of examples to demonstrate how to solve common and complex problems with the Composable Architecture. Check out this directory to see them all, including:
- Case Studies
- Getting started
- Effects
- Navigation
- Higher-order reducers
- Reusable components
- Location manager
- Motion manager
- Search
- Speech Recognition
- SyncUps app
- Tic-Tac-Toe
- Todos
- Voice memos
Looking for something more substantial? Check out the source code for isowords, an iOS word search game built in SwiftUI and the Composable Architecture.
Basic Usage
[!Note] For a step-by-step interactive tutorial, be sure to check out Meet the Composable Architecture.
To build a feature using the Composable Architecture you define some types and values that model your domain:
- State: A type that describes the data your feature needs to perform its logic and render its UI.
- Action: A type that represents all of the actions that can happen in your feature, such as user actions, notifications, event sources and more.
- Reducer: A function that describes how to evolve the current state of the app to the next
state given an action. The reducer is also responsible for returning any effects that should be
run, such as API requests, which can be done by returning an
Effect
value. - Store: The runtime that actually drives your feature. You send all user actions to the store so that the store can run the reducer and effects, and you can observe state changes in the store so that you can update UI.
The benefits of doing this are that you will instantly unlock testability of your feature, and you will be able to break large, complex features into smaller domains that can be glued together.
As a basic example, consider a UI that shows a number along with "+" and "â" buttons that increment and decrement the number. To make things interesting, suppose there is also a button that when tapped makes an API request to fetch a random fact about that number and displays it in the view.
To implement this feature we create a new type that will house the domain and behavior of the
feature, and it will be annotated with the @Reducer
macro:
import ComposableArchitecture
@Reducer
struct Feature {
}
In here we need to define a type for the feature's state, which consists of an integer for the current count, as well as an optional string that represents the fact being presented:
@Reducer
struct Feature {
@ObservableState
struct State: Equatable {
var count = 0
var numberFact: String?
}
}
[!Note] We've applied the
@ObservableState
macro toState
in order to take advantage of the observation tools in the library.
We also need to define a type for the feature's actions. There are the obvious actions, such as tapping the decrement button, increment button, or fact button. But there are also some slightly non-obvious ones, such as the action that occurs when we receive a response from the fact API request:
@Reducer
struct Feature {
@ObservableState
struct State: Equatable { /* ... */ }
enum Action {
case decrementButtonTapped
case incrementButtonTapped
case numberFactButtonTapped
case numberFactResponse(String)
}
}
And then we implement the body
property, which is responsible for composing the actual logic and
behavior for the feature. In it we can use the Reduce
reducer to describe how to change the
current state to the next state, and what effects need to be executed. Some actions don't need to
execute effects, and they can return .none
to represent that:
@Reducer
struct Feature {
@ObservableState
struct State: Equatable { /* ... */ }
enum Action { /* ... */ }
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .decrementButtonTapped:
state.count -= 1
return .none
case .incrementButtonTapped:
state.count += 1
return .none
case .numberFactButtonTapped:
return .run { [count = state.count] send in
let (data, _) = try await URLSession.shared.data(
from: URL(string: "http://numbersapi.com/\(count)/trivia")!
)
await send(
.numberFactResponse(String(decoding: data, as: UTF8.self))
)
}
case let .numberFactResponse(fact):
state.numberFact = fact
return .none
}
}
}
}
And then finally we define the view that displays the feature. It holds onto a StoreOf<Feature>
so that it can observe all changes to the state and re-render, and we can send all user actions to
the store so that state changes:
struct FeatureView: View {
let store: StoreOf<Feature>
var body: some View {
Form {
Section {
Text("\(store.count)")
Button("Decrement") { store.send(.decrementButtonTapped) }
Button("Increment") { store.send(.incrementButtonTapped) }
}
Section {
Button("Number fact") { store.send(.numberFactButtonTapped) }
}
if let fact = store.numberFact {
Text(fact)
}
}
}
}
It is also straightforward to have a UIKit controller driven off of this store. You can observe
state changes in the store in viewDidLoad
, and then populate the UI components with data from
the store. The code is a bit longer than the SwiftUI version, so we have collapsed it here:
Click to expand!
class FeatureViewController: UIViewController {
let store: StoreOf<Feature>
init(store: StoreOf<Feature>) {
self.store = store
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let countLabel = UILabel()
let decrementButton = UIButton()
let incrementButton = UIButton()
let factLabel = UILabel()
// Omitted: Add subviews and set up constraints...
observe { [weak self] in
guard let self
else { return }
countLabel.text = "\(self.store.text)"
factLabel.text = self.store.numberFact
}
}
@objc private func incrementButtonTapped() {
self.store.send(.incrementButtonTapped)
}
@objc private func decrementButtonTapped() {
self.store.send(.decrementButtonTapped)
}
@objc private func factButtonTapped() {
self.store.send(.numberFactButtonTapped)
}
}
Once we are ready to display this view, for example in the app's entry point, we can construct a store. This can be done by specifying the initial state to start the application in, as well as the reducer that will power the application:
import ComposableArchitecture
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
FeatureView(
store: Store(initialState: Feature.State()) {
Feature()
}
)
}
}
}
And that is enough to get something on the screen to play around with. It's definitely a few more steps than if you were to do this in a vanilla SwiftUI way, but there are a few benefits. It gives us a consistent manner to apply state mutations, instead of scattering logic in some observable objects and in various action closures of UI components. It also gives us a concise way of expressing side effects. And we can immediately test this logic, including the effects, without doing much additional work.
Testing
[!Note] For more in-depth information on testing, see the dedicated testing article.
To test use a TestStore
, which can be created with the same information as the Store
, but it
does extra work to allow you to assert how your feature evolves as actions are sent:
@Test
func basics() async {
let store = TestStore(initialState: Feature.State()) {
Feature()
}
}
Once the test store is created we can use it to make an assertion of an entire user flow of steps. Each step of the way we need to prove that state changed how we expect. For example, we can simulate the user flow of tapping on the increment and decrement buttons:
// Test that tapping on the increment/decrement buttons changes the count
await store.send(.incrementButtonTapped) {
$0.count = 1
}
await store.send(.decrementButtonTapped) {
$0.count = 0
}
Further, if a step causes an effect to be executed, which feeds data back into the store, we must
assert on that. For example, if we simulate the user tapping on the fact button we expect to
receive a fact response back with the fact, which then causes the numberFact
state to be
populated:
await store.send(.numberFactButtonTapped)
await store.receive(\.numberFactResponse) {
$0.numberFact = ???
}
However, how do we know what fact is going to be sent back to us?
Currently our reducer is using an effect that reaches out into the real world to hit an API server, and that means we have no way to control its behavior. We are at the whims of our internet connectivity and the availability of the API server in order to write this test.
It would be better for this dependency to be passed to the reducer so that we can use a live
dependency when running the application on a device, but use a mocked dependency for tests. We can
do this by adding a property to the Feature
reducer:
@Reducer
struct Feature {
let numberFact: (Int) async throws -> String
// ...
}
Then we can use it in the reduce
implementation:
case .numberFactButtonTapped:
return .run { [count = state.count] send in
let fact = try await self.numberFact(count)
await send(.numberFactResponse(fact))
}
And in the entry point of the application we can provide a version of the dependency that actually interacts with the real world API server:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
FeatureView(
store: Store(initialState: Feature.State()) {
Feature(
numberFact: { number in
let (data, _) = try await URLSession.shared.data(
from: URL(string: "http://numbersapi.com/\(number)")!
)
return String(decoding: data, as: UTF8.self)
}
)
}
)
}
}
}
But in tests we can use a mock dependency that immediately returns a deterministic, predictable fact:
@Test
func basics() async {
let store = TestStore(initialState: Feature.State()) {
Feature(numberFact: { "\($0) is a good number Brent" })
}
}
With that little bit of upfront work we can finish the test by simulating the user tapping on the fact button, and then receiving the response from the dependency to present the fact:
await store.send(.numberFactButtonTapped)
await store.receive(\.numberFactResponse) {
$0.numberFact = "0 is a good number Brent"
}
We can also improve the ergonomics of using the numberFact
dependency in our application. Over
time the application may evolve into many features, and some of those features may also want access
to numberFact
, and explicitly passing it through all layers can get annoying. There is a process
you can follow to âregisterâ dependencies with the library, making them instantly available to any
layer in the application.
[!Note] For more in-depth information on dependency management, see the dedicated dependencies article.
We can start by wrapping the number fact functionality in a new type:
struct NumberFactClient {
var fetch: (Int) async throws -> String
}
And then registering that type with the dependency management system by conforming the client to
the DependencyKey
protocol, which requires you to specify the live value to use when running the
application in simulators or devices:
extension NumberFactClient: DependencyKey {
static let liveValue = Self(
fetch: { number in
let (data, _) = try await URLSession.shared
.data(from: URL(string: "http://numbersapi.com/\(number)")!
)
return String(decoding: data, as: UTF8.self)
}
)
}
extension DependencyValues {
var numberFact: NumberFactClient {
get { self[NumberFactClient.self] }
set { self[NumberFactClient.self] = newValue }
}
}
With that little bit of upfront work done you can instantly start making use of the dependency in
any feature by using the @Dependency
property wrapper:
@Reducer
struct Feature {
- let numberFact: (Int) async throws -> String
+ @Dependency(\.numberFact) var numberFact
â¦
- try await self.numberFact(count)
+ try await self.numberFact.fetch(count)
}
This code works exactly as it did before, but you no longer have to explicitly pass the dependency when constructing the feature's reducer. When running the app in previews, the simulator or on a device, the live dependency will be provided to the reducer, and in tests the test dependency will be provided.
This means the entry point to the application no longer needs to construct dependencies:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
FeatureView(
store: Store(initialState: Feature.State()) {
Feature()
}
)
}
}
}
And the test store can be constructed without specifying any dependencies, but you can still override any dependency you need to for the purpose of the test:
let store = TestStore(initialState: Feature.State()) {
Feature()
} withDependencies: {
$0.numberFact.fetch = { "\($0) is a good number Brent" }
}
// ...
That is the basics of building and testing a feature in the Composable Architecture. There are a lot more things to be explored, such as composition, modularity, adaptability, and complex effects. The Examples directory has a bunch of projects to explore to see more advanced usages.
Documentation
The documentation for releases and main
are available here:
Other versions
- 1.16.0 (migration guide)
- 1.15.0 (migration guide)
- 1.14.0 (migration guide)
- 1.13.0 (migration guide)
- 1.12.0 (migration guide)
- 1.11.0 (migration guide)
- 1.10.0 (migration guide)
- 1.9.0 (migration guide)
- 1.8.0 (migration guide)
- 1.7.0 (migration guide)
- 1.6.0 (migration guide)
- 1.5.0 (migration guide)
- 1.4.0 (migration guide)
- 1.3.0
- 1.2.0
- 1.1.0
- 1.0.0
- 0.59.0
- 0.58.0
- 0.57.0
There are a number of articles in the documentation that you may find helpful as you become more comfortable with the library:
Community
If you want to discuss the Composable Architecture or have a question about how to use it to solve a particular problem, there are a number of places you can discuss with fellow Point-Free enthusiasts:
- For long-form discussions, we recommend the discussions tab of this repo.
- For casual chat, we recommend the Point-Free Community slack.
Installation
You can add ComposableArchitecture to an Xcode project by adding it as a package dependency.
- From the File menu, select Add Package Dependencies...
- Enter "https://github.com/pointfreeco/swift-composable-architecture" into the package repository URL text field
- Depending on how your project is structured:
- If you have a single application target that needs access to the library, then add ComposableArchitecture directly to your application.
- If you want to use this library from multiple Xcode targets, or mix Xcode targets and SPM targets, you must create a shared framework that depends on ComposableArchitecture and then depend on that framework in all of your targets. For an example of this, check out the Tic-Tac-Toe demo application, which splits lots of features into modules and consumes the static library in this fashion using the tic-tac-toe Swift package.
Companion libraries
The Composable Architecture is built with extensibility in mind, and there are a number of community-supported libraries available to enhance your applications:
- Composable Architecture Extras: A companion library to the Composable Architecture.
- TCAComposer: A macro framework for generating boiler-plate code in the Composable Architecture.
- TCACoordinators: The coordinator pattern in the Composable Architecture.
If you'd like to contribute a library, please open a PR with a link to it!
Translations
The following translations of this README have been contributed by members of the community:
- Arabic
- French
- Hindi
- Indonesian
- Italian
- Japanese
- Korean
- Polish
- Portuguese
- Russian
- Simplified Chinese
- Spanish
- Ukrainian
If you'd like to contribute a translation, please open a PR with a link to a Gist!
FAQ
We have a dedicated article for all of the most frequently asked questions and comments people have concerning the library.
Credits and thanks
The following people gave feedback on the library at its early stages and helped make the library what it is today:
Paul Colton, Kaan Dedeoglu, Matt Diephouse, Josef Doležal, Eimantas, Matthew Johnson, George Kaimakas, Nikita Leonov, Christopher Liscio, Jeffrey Macko, Alejandro Martinez, Shai Mishali, Willis Plummer, Simon-Pierre Roy, Justin Price, Sven A. Schmidt, Kyle Sherman, Petr Å Ãma, Jasdev Singh, Maxim Smirnov, Ryan Stone, Daniel Hollis Tavares, and all of the Point-Free subscribers ð.
Special thanks to Chris Liscio who helped us work through many strange SwiftUI quirks and helped refine the final API.
And thanks to Shai Mishali and the
CombineCommunity project, from which we took
their implementation of Publishers.Create
, which we use in Effect
to help bridge delegate and
callback-based APIs, making it much easier to interface with 3rd party frameworks.
Other libraries
The Composable Architecture was built on a foundation of ideas started by other libraries, in particular Elm and Redux.
There are also many architecture libraries in the Swift and iOS community. Each one of these has their own set of priorities and trade-offs that differ from the Composable Architecture.
-
And more
License
This library is released under the MIT license. See LICENSE for details.
Top Related Projects
Reactive Programming in Swift
Unidirectional Data Flow in Swift - Inspired by Redux
A Swift binding framework
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.
Network abstraction layer written in Swift.
Dependency injection framework for Swift with iOS/macOS/Linux
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot