Core is a framework for making more reactive applications inspired by ReactorKit, Redux.
Core is Reactive independent Framework which means you can expand whatever you want to import such as Combine, RxSwift.
It's a very light weigthed and simple architecture, so you can either use CocoaPods or SPM to stay up to date, or just drag and drop into your project and go. Or you can look through it and roll your own.
CoreEngine is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'CoreEngine'Swift Package Manager is a tool for managing the distribution of Swift code. It's integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
To integrate CoreEngine into your Xcode project using Swift Package Manager, add it to the dependencies value of your Package.swift:
dependencies: [
.package(url: "https://github.com/sobabear/CoreEngine.git", .upToNextMajor(from: "2.0.0"))
]Core Engine is insanely fast and light-weight compared to similar frameworks you can check details here CoreEngineBenchMark
While CoreEngine provides both traditional reactive approaches and state management patterns, we highly recommend using AsyncCore for modern, asynchronous, and more efficient state handling.
AsyncCore leverages Swift's structured concurrency with async/await, providing a clean and intuitive way to manage state updates, handle side effects, and ensure thread safety with Swift's Actor model.
-
Concurrency-First: Built for Swift's native concurrency model, using async/await to handle asynchronous actions.
-
Simplified State Management: One stored property (
store) replaces the previous three-property boilerplate. -
Data-Race Free: State is lock-protected via
OSAllocatedUnfairLock; nononisolated(unsafe)required. -
Memory Safe: Cancelled subscribers are automatically removed from the broadcaster.
-
Deduplication: State emissions are skipped when
reducereturns an equal state. -
Thread Safety: Built on Swift's Actor system with Swift 6 strict concurrency.
/// ViewController with Core let label = UILabel() let increaseButton = UIButton() let decreaseButton = UIButton() var core: MainCore = .init() func increaseButtonTapped() { self.core.action(.increase) } func decreaseButtonTapped() { self.core.action(.decrease) } func multipleActions() { self.core(.increase, .decrease) // @dynamicCallable syntax } func bind() { core.$state.map(\.count) .sink { [weak self] count in self?.label.text = "\(count)" } .store(in: &subscription) } /// ViewController with AsyncCore (v2) class ViewController: UIViewController { private var core: AsyncMainCore? override func viewDidLoad() { super.viewDidLoad() // init is synchronous in v2 β no await needed let core = AsyncMainCore(initialState: .init()) self.core = core self.bind(core: core) } private func bind(core: AsyncMainCore) { Task { for await count in core.states.map(\.count) { print("Count: \(count)") } } Task { // @dynamicMemberLookup: iterate a single property directly for await count in core.states.count { print("Count: \(count)") } } } @IBAction func increaseTapped() { core?.send(.increase) } @IBAction func decreaseTapped() { core?.send(.decrease) } }
// AsyncCore conformance (v2) β one stored property, synchronous init actor AsyncMainCore: AsyncCore { let store: AsyncCoreStore<State> enum Action: Sendable { case increase case decrease } struct State: Equatable, Sendable { var count = 0 } init(initialState: State) { self.store = .init(initialState: initialState) } func reduce(state: State, action: Action) async throws -> State { var newState = state switch action { case .increase: newState.count += 1 case .decrease: newState.count -= 1 } return newState } } // Core conformance β unchanged call syntax class MainCore: Core { enum Action: Equatable, Hashable { case increase case decrease } struct State: Equatable, Sendable { var count = 0 } @Published var state: State = .init() func reduce(state: State, action: Action) -> State { var newState = state switch action { case .decrease: newState.count -= 1 case .increase: newState.count += 1 } return newState } }
Not just simple core, but complex core is also supported. For example, Side Effect and Error handling. When it comes to those, you use AsyncCore or PublisherCore.
This method is defined in PublisherCore and when you deal with side-effect generated publisher send into the function. Also you can handle every errors on the handleError(error: Error) function.
class MainCore: PublisherCore {
var subscription: Set<AnyCancellable> = .init()
enum Action {
case increase
case decrease
case setNumber(Int)
}
struct State: Equatable, Sendable {
var count = 0
}
@Published var state: State = .init()
private let sessionService = SessionService()
init() {
dispatch(effect: sessionService.randomUInt$().map(Action.setNumber))
}
func reduce(state: State, action: Action) -> State {
var newState = state
switch action {
case .increase: newState.count += 1
case .decrease: newState.count -= 1
case let .setNumber(value): newState.count = value
}
return newState
}
func handleError(error: Error) {
// handle
}
}| Breaking change | Migration |
|---|---|
State must conform to Equatable & Sendable |
Add conformances to all State structs |
AsyncCore removes currentState, states, continuation protocol requirements |
Replace 3 properties + async init with let store: AsyncCoreStore<State> + sync init |
AsyncCore.init is no longer async |
Remove await from init call sites |
Core.action is a method, not a closure property |
core.action(.x) unchanged; let fn = core.action no longer compiles |
| iOS 13/14/15 no longer supported | Update deployment targets to iOS 16+ |
actor MyCore: AsyncCore {
nonisolated(unsafe) var currentState: State
var states: AsyncCoreSequence<State>
var continuation: AsyncStream<State>.Continuation
init(initialState: State) async {
self.currentState = initialState
let (stream, continuation) = AsyncStream<State>.makeStream()
self.states = await .init(stream)
self.continuation = continuation
}
}
// Call site
let core = await MyCore(initialState: .init())actor MyCore: AsyncCore {
let store: AsyncCoreStore<State>
init(initialState: State) {
self.store = .init(initialState: initialState)
}
}
// Call site β no await
let core = MyCore(initialState: .init())import Foundation
import CoreEngine
import RxSwift
protocol RxCore: Core {
var disposeBag: DisposeBag { get set }
func mutate(effect: Observable<Action>)
func handleError(error: Error)
}
extension RxCore {
public func mutate(effect: Observable<Action>) {
effect
.subscribe(onNext: { [weak self] in
self?.action($0)
}, onError: { [weak self] in
self?.handleError(error: $0)
})
.disposed(by: disposeBag)
}
public func handleError(error: Error) { }
}stareta1202, stareta1202@gmail.com
CoreEngine is available under the MIT license. See the LICENSE file for more info.
