OneWay is a simple and lightweight library for state management with unidirectional data flow. It is fully supported for using anywhere that uses Swift. You can use it on any platform and with any framework. There are no dependencies on third parties, so you can use OneWay purely. It can not only be used in the presentation layer, but can also be used to simplify complex business logic. It will be useful whenever you want to design logic in unidirection.
It is easy to think of a Way as a path through which data passes. You can inherit a Way and should implement as below. It is also freely customizable and encapsulatable, since Way is a class.
final class CounterWay: Way<CounterWay.Action, CounterWay.State> {
enum Action {
case increment
case decrement
case twice
}
struct State: Equatable {
var number: Int
}
override func reduce(state: inout State, action: Action) -> SideWay<Action, Never> {
switch action {
case .increment:
state.number += 1
return .none
case .decrement:
state.number -= 1
return .none
case .twice:
return .concat(
.just(.increment),
.just(.increment)
)
}
}
}
Sending Actions
Sending an action to a Way causes changes in the state via reduce().
let way = CounterWay(initialState: .init(number: 0))
way.send(.increment)
way.send(.decrement)
way.send(.twice)
print(way.state.number) // 2
Subscribing a Way
When a value changes, it can receive a new value. It guarantees that the same value does not come down consecutively. In general, you don’t need to add removeDuplicates(). But if you want to receive all values when the way’s state changes, use map operator to way’s publisher.
// number <- 10, 10, 20 ,20
way.publisher.number
.sink { number in
print(number) // 10, 20
}
.store(in: &cancellables)
way.publisher.map(\.number)
.sink { number in
print(number) // 10, 10, 20, 20
}
.store(in: &cancellables)
}
Global States
You can easily subscribe to global states by overriding bind().
final class CounterWay: Way<CounterWay.Action, CounterWay.State> {
enum Action {
case fetchNumber
case setNumber(Int)
}
struct State: Equatable {
var number: Int
}
override func reduce(state: inout State, action: Action) -> SideWay<Action, Never> {
switch action {
case .fetchNumber:
return .async {
let number = await fetchNumber()
return Action.setNumber(number)
}
case .setNumber(let number):
state.number = number
return .none
}
}
}
Supporting NSObject
Way is a class, not a protocol. Therefore, multiple inheritance is not possible. There are often situations where you have to inherit NSObject. NSWay was added for this occasion. In this case, inherit and implement NSWay, and in other cases, inherit Way.
final class CounterWay: NSWay<CounterWay.Action, CounterWay.State> {
// ...
}
Thread Safe or Not
Way has a ThreadOption to consider the multithreaded environment. This option can be passed as an argument to the initializer. Once set, it cannot be changed. In a general environment, it is better to use the default option(current) for better performance. But, if it is initialized with the current option, all interactions (i.e. sending actions) with an instance of Way must be done on the same thread.
let way = CounterWay(initialState: initialState, threadOption: .current)
let threadSafeWay = CounterWay(initialState: initialState, threadOption: .threadSafe)
Documentation
Learn how to use OneWay by going through the documentation created using DocC.
Benchmark
Compared to other libraries, OneWay shows very good performance.
OneWay is a simple and lightweight library for state management with unidirectional data flow. It is fully supported for using anywhere that uses Swift. You can use it on any platform and with any framework. There are no dependencies on third parties, so you can use OneWay purely. It can not only be used in the presentation layer, but can also be used to simplify complex business logic. It will be useful whenever you want to design logic in unidirection.
Data Flow
Usage
Implementing a Way
It is easy to think of a Way as a path through which data passes. You can inherit a Way and should implement as below. It is also freely customizable and encapsulatable, since Way is a class.
Sending Actions
Sending an action to a Way causes changes in the
state
viareduce()
.Subscribing a Way
When a value changes, it can receive a new value. It guarantees that the same value does not come down consecutively. In general, you don’t need to add
removeDuplicates()
. But if you want to receive all values when the way’s state changes, usemap
operator to way’s publisher.Global States
You can easily subscribe to global states by overriding
bind()
.Catching Errors
There are several functions that handle errors. It is a little easier to understand if you refer to unit tests.
Swift Concurrency
async/await
can also be used with Way.Supporting NSObject
Way is a class, not a protocol. Therefore, multiple inheritance is not possible. There are often situations where you have to inherit NSObject. NSWay was added for this occasion. In this case, inherit and implement NSWay, and in other cases, inherit Way.
Thread Safe or Not
Way has a
ThreadOption
to consider the multithreaded environment. This option can be passed as an argument to the initializer. Once set, it cannot be changed. In a general environment, it is better to use the default option(current
) for better performance. But, if it is initialized with thecurrent
option, all interactions (i.e. sending actions) with an instance of Way must be done on the same thread.Documentation
Learn how to use OneWay by going through the documentation created using DocC.
Benchmark
Compared to other libraries, OneWay shows very good performance.
For more details, 👉 OneWayBenchmark
Examples
Requirements
Installation
OneWay is only supported by Swift Package Manager.
To integrate OneWay into your Xcode project using Swift Package Manager, add it to the dependencies value of your
Package.swift
:Next Step
References
These are the references that inspired OneWay a lot.
License
This library is released under the MIT license. See LICENSE for details.