A Simple and light-weighted unidirectional architecture in Swift
CoreEngine
Simple and light
Scalability
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.
Installation
CoreEngine is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'CoreEngine'
Example
See details on Example
// on ViewController
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 bind() {
core.$state.map(\.count)
.sink { [weak self] count in
self?.label.text = "\(count)"
}
.store(in: &subscription)
}
...
class MainCore: Core {
var subscription: Set<AnyCancellable> = .init()
enum Action: Equatable, Hashable {
case increase
case decrease
}
struct State: Equatable {
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
}
}
Side Effect & Error Handling
Not just simple core, but complex core is also supported. For example, Side Effect and Error handling. When it comes to those, you use AnyCore
.
It is not very different from Core, since AnyCore also conforms.
func dispatch(effect: any Publisher) & func handleError(error: Error)
This method is defined in AnyCore 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
Here is an example of the AnyCore
class MainCore: AnyCore {
var subscription: Set<AnyCancellable> = .init()
enum Action {
case increase
case decrease
case jump(Int)
case setNumber(Int)
}
struct State {
var count = 0
}
@Published var state: State = .init()
@Published var tenGap: Int = 10
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 .jump(value):
newState.count += value
case let .setNumber(value):
newState.count = value
}
return newState
}
func handleError(error: Error) {
if let errpr = error as? MyError {
//handle
}
}
func tenJumpAction() {
self.dispatch(effect: $tenGap.map(Action.jump))
}
}
class SessionService {
func randomUInt$() -> AnyPublisher<Int, Error> {
// blahblah
}
}
Examples + RxSwift
copy those code for RxSwift
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
if let self {
self.state = self.reduce(state: self.state, action: $0)
}
}, onError: { [weak self] in
self?.handleError(error: $0)
})
.disposed(by: disposeBag)
}
public func handleError(error: Error) { }
}
@propertyWrapper
class ObservableProperty<Element>: ObservableType {
var wrappedValue: Element {
didSet {
subject.onNext(wrappedValue)
}
}
private let subject: BehaviorSubject<Element>
init(wrappedValue: Element) {
self.wrappedValue = wrappedValue
self.subject = BehaviorSubject<Element>(value: wrappedValue)
}
var projectedValue: Observable<Element> {
return subject.asObservable()
}
func subscribe<Observer>(_ observer: Observer) -> Disposable where Observer : ObserverType, Element == Observer.Element {
return subject.subscribe(observer)
}
}
Author
stareta1202, stareta1202@gmail.com
License
CoreEngine is available under the MIT license. See the LICENSE file for more info.