Skip to content

Improve protocols naming, usability and fix minor bugs #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 58 additions & 38 deletions Mini.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Sources/Actions/Action.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Foundation

public protocol Action {
func isEqual(to other: Action) -> Bool
}

extension Action {
Expand Down
6 changes: 0 additions & 6 deletions Sources/Middleware.swift → Sources/Chain.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import Foundation

public typealias MiddlewareChain = (Action, Chain) -> Action
public typealias Next = (Action) -> Action

public protocol Chain {
var proceed: Next { get }
}

public protocol Middleware {
var id: UUID { get }
var perform: MiddlewareChain { get }
}

public final class ForwardingChain: Chain {
private let next: Next

Expand Down
43 changes: 9 additions & 34 deletions Sources/Dispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ public final class Dispatcher {

private let internalQueue = DispatchQueue(label: "MiniSwift", qos: .userInitiated)
private var subscriptionMap = SubscriptionMap()
private var middleware = [Middleware]()
private var service = [ServiceType]()
private var interceptors = [Interceptor]()
private let root: RootChain
private var chain: Chain
private var dispatching = false
Expand All @@ -27,40 +26,16 @@ public final class Dispatcher {
chain = root
}

private func build() -> Chain {
middleware.reduce(root) { (chain: Chain, middleware: Middleware) -> Chain in
ForwardingChain { action in
middleware.perform(action, chain)
}
}
}

public func add(middleware: Middleware) {
internalQueue.sync {
self.middleware.append(middleware)
self.chain = build()
}
}

public func remove(middleware: Middleware) {
internalQueue.sync {
if let index = self.middleware.firstIndex(where: { middleware.id == $0.id }) {
self.middleware.remove(at: index)
}
chain = build()
}
}

public func register(service: ServiceType) {
public func register(interceptor: Interceptor) {
internalQueue.sync {
self.service.append(service)
self.interceptors.append(interceptor)
}
}

public func unregister(service: ServiceType) {
public func unregister(interceptor: Interceptor) {
internalQueue.sync {
if let index = self.service.firstIndex(where: { service.id == $0.id }) {
self.service.remove(at: index)
if let index = self.interceptors.firstIndex(where: { interceptor.id == $0.id }) {
self.interceptors.remove(at: index)
}
}
}
Expand Down Expand Up @@ -122,10 +97,10 @@ public final class Dispatcher {
}
}

internal func stateWasReplayed(state: StateType) {
internal func stateWasReplayed(state: any State) {
internalQueue.async { [weak self] in
guard let self = self else { return }
self.service.forEach {
self.interceptors.forEach {
$0.stateWasReplayed(state: state)
}
}
Expand All @@ -141,7 +116,7 @@ public final class Dispatcher {
_ = chain.proceed(action)
internalQueue.async { [weak self] in
guard let self = self else { return }
self.service.forEach {
self.interceptors.forEach {
$0.perform(action, self.chain)
}
}
Expand Down
9 changes: 9 additions & 0 deletions Sources/Interceptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

public typealias InterceptorChain = (Action, Chain) -> Void

public protocol Interceptor {
var id: UUID { get }
var perform: InterceptorChain { get }
func stateWasReplayed(state: any State)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public extension Publisher {
break
}
} receiveValue: { payload in
let action = A(task: .success(payload, tag: "\(key)"), key: key)
let action = A(task: .success(payload, expiration: expiration, tag: "\(key)"), key: key)
dispatcher.dispatch(action)
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Publishers/Publishers.CombineMiniTasksArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Combine
import Foundation

public extension Publisher {
func combineMiniTasks<T: TaskType>()
func combineMiniTasks<T: Taskable>()
-> Publishers.CombineMiniTasksArray<Self, [T.Payload], T.Failure>
where Output == [T] {
Publishers.CombineMiniTasksArray(upstream: self)
Expand Down Expand Up @@ -41,7 +41,7 @@ extension Publishers.CombineMiniTasksArray {
}

func receive(_ input: Upstream.Output) -> Subscribers.Demand {
guard let tasks = input as? [any TaskType] else {
guard let tasks = input as? [any Taskable] else {
fatalError("Imposible!")
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Publishers/Publishers.CombineMiniTasksTuple2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public struct TaskTuple2Payload<T1P: Equatable, T2P: Equatable>: TaskTuple2Paylo
}

public extension Publisher {
func combineMiniTasks<T1: TaskType, T2: TaskType>()
func combineMiniTasks<T1: Taskable, T2: Taskable>()
-> Publishers.CombineMiniTasksTuple2<Self, TaskTuple2Payload<T1.Payload, T2.Payload>, T1.Failure>
where Output == (T1, T2), T1.Failure == T2.Failure {
Publishers.CombineMiniTasksTuple2(upstream: self)
Expand Down Expand Up @@ -61,7 +61,7 @@ extension Publishers.CombineMiniTasksTuple2 {
}

func receive(_ input: Upstream.Output) -> Subscribers.Demand {
guard let tuple = input as? (any TaskType, any TaskType) else {
guard let tuple = input as? (any Taskable, any Taskable) else {
fatalError("Imposible!")
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Publishers/Publishers.CombineMiniTasksTuple3.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public struct TaskTuple3Payload<T1P: Equatable, T2P: Equatable, T3P: Equatable>:
}

public extension Publisher {
func combineMiniTasks<T1: TaskType, T2: TaskType, T3: TaskType>()
func combineMiniTasks<T1: Taskable, T2: Taskable, T3: Taskable>()
-> Publishers.CombineMiniTasksTuple3<Self, TaskTuple3Payload<T1.Payload, T2.Payload, T3.Payload>, T1.Failure>
where Output == (T1, T2, T3), T1.Failure == T2.Failure, T1.Failure == T2.Failure {
Publishers.CombineMiniTasksTuple3(upstream: self)
Expand Down Expand Up @@ -65,7 +65,7 @@ extension Publishers.CombineMiniTasksTuple3 {
}

func receive(_ input: Upstream.Output) -> Subscribers.Demand {
guard let tuple = input as? (any TaskType, any TaskType, any TaskType) else {
guard let tuple = input as? (any Taskable, any Taskable, any Taskable) else {
fatalError("Imposible!")
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Publishers/Publishers.CombineMiniTasksTuple4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct TaskTuple4Payload<T1P: Equatable, T2P: Equatable, T3P: Equatable,
}

public extension Publisher {
func combineMiniTasks<T1: TaskType, T2: TaskType, T3: TaskType, T4: TaskType>()
func combineMiniTasks<T1: Taskable, T2: Taskable, T3: Taskable, T4: Taskable>()
-> Publishers.CombineMiniTasksTuple4<Self, TaskTuple4Payload<T1.Payload, T2.Payload, T3.Payload, T4.Payload>, T1.Failure>
where Output == (T1, T2, T3, T4), T1.Failure == T2.Failure, T1.Failure == T3.Failure, T1.Failure == T4.Failure {
Publishers.CombineMiniTasksTuple4(upstream: self)
Expand Down Expand Up @@ -69,7 +69,7 @@ extension Publishers.CombineMiniTasksTuple4 {
}

func receive(_ input: Upstream.Output) -> Subscribers.Demand {
guard let tuple = input as? (any TaskType, any TaskType, any TaskType, any TaskType) else {
guard let tuple = input as? (any Taskable, any Taskable, any Taskable, any Taskable) else {
fatalError("Imposible!")
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Publishers/Publishers.EraseToEmptyTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import Combine

public extension Publisher {
func eraseToEmptyTask() -> Publishers.EraseToEmptyTask<Self, Output.Failure>
where Output: TaskType {
where Output: Taskable {
Publishers.EraseToEmptyTask(upstream: self)
}
}

public extension Publishers {
/// Create a `Publisher` that connect an Upstream (Another publisher) that type erases `Task`s to `EmptyTask`
/// The Output of this `Publisher` always is a combined `EmptyTask`
struct EraseToEmptyTask<Upstream: Publisher, TaskFailure: Error>: Publisher where Upstream.Output: TaskType, Upstream.Output.Failure == TaskFailure {
struct EraseToEmptyTask<Upstream: Publisher, TaskFailure: Error>: Publisher where Upstream.Output: Taskable, Upstream.Output.Failure == TaskFailure {
public typealias Output = EmptyTask<TaskFailure>
public typealias Failure = Upstream.Failure

Expand Down
9 changes: 0 additions & 9 deletions Sources/ServiceType.swift

This file was deleted.

4 changes: 4 additions & 0 deletions Sources/State.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Foundation

public protocol State: Equatable {
}
12 changes: 0 additions & 12 deletions Sources/StateType.swift

This file was deleted.

70 changes: 26 additions & 44 deletions Sources/Store.swift
Original file line number Diff line number Diff line change
@@ -1,66 +1,31 @@
import Combine
import Foundation

public protocol StoreType {
associatedtype State: StateType
associatedtype StoreController: Cancellable

var state: State { get set }
var dispatcher: Dispatcher { get }
var reducerGroup: ReducerGroup { get }
}

extension StoreType {
/**
Property responsible of reduce the `State` given a certain `Action` being triggered.
```
public var reducerGroup: ReducerGroup {
ReducerGroup {[
Reducer(of: SomeAction.self, on: self.dispatcher) { (action: SomeAction)
self.state = myCoolNewState
},
Reducer(of: OtherAction.self, on: self.dispatcher) { (action: OtherAction)
// Needed work
self.state = myAnotherState
}
}
]}
```
- Note : The property has a default implementation which complies with the @_functionBuilder's current limitations, where no empty blocks can be produced in this iteration.
*/
public var reducerGroup: ReducerGroup {
ReducerGroup {
[]
}
}
}

public class Store<State: StateType, StoreController: Cancellable>: Publisher, StoreType {
public typealias Output = State
public class Store<StoreState: State, StoreController: Cancellable>: Publisher {
public typealias Output = StoreState
public typealias Failure = Never
public typealias State = State
public typealias StoreController = StoreController

public let dispatcher: Dispatcher
public var storeController: StoreController
public var state: State {
public var state: StoreState {
get {
_state
}
set {
queue.sync {
if !newValue.isEqual(to: _state) {
if newValue != _state {
_state = newValue
objectWillChange.send(state)
}
}
}
}
public var initialState: State {
public var initialState: StoreState {
_initialState
}

public init(_ state: State,
public init(_ state: StoreState,
dispatcher: Dispatcher,
storeController: StoreController) {
self._initialState = state
Expand All @@ -71,6 +36,23 @@ public class Store<State: StateType, StoreController: Cancellable>: Publisher, S
self.state = _initialState
}

/**
Property responsible of reduce the `State` given a certain `Action` being triggered.
```
public var reducerGroup: ReducerGroup {
ReducerGroup {[
Reducer(of: SomeAction.self, on: self.dispatcher) { (action: SomeAction)
self.state = myCoolNewState
},
Reducer(of: OtherAction.self, on: self.dispatcher) { (action: OtherAction)
// Needed work
self.state = myAnotherState
}
}
]}
```
- Note : The property has a default implementation which complies with the @_functionBuilder's current limitations, where no empty blocks can be produced in this iteration.
*/
public var reducerGroup: ReducerGroup {
ReducerGroup {
[]
Expand All @@ -91,8 +73,8 @@ public class Store<State: StateType, StoreController: Cancellable>: Publisher, S
objectWillChange.subscribe(subscriber)
}

private var objectWillChange: CurrentValueSubject<State, Never>
private var objectWillChange: CurrentValueSubject<StoreState, Never>
private let queue = DispatchQueue(label: "atomic state")
private var _initialState: State
private var _state: State
private var _initialState: StoreState
private var _state: StoreState
}
3 changes: 3 additions & 0 deletions Sources/Task/EmptyTask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Foundation

public typealias EmptyTask<E: Error & Equatable> = Task<None, E>
7 changes: 4 additions & 3 deletions Sources/Task/KeyedTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import Foundation
public typealias KeyedTask<Key: Hashable, TaskPayload: Equatable, TaskError: Error & Equatable> = [Key: Task<TaskPayload, TaskError>]
public typealias KeyedEmptyTask<Key: Hashable, TaskError: Error & Equatable> = KeyedTask<Key, None, TaskError>

extension KeyedTask where Key: Hashable, Value: TaskType {
public subscript(task key: Key) -> Value? {
self[key]
extension KeyedTask where Key: Hashable, Value: Taskable {
/// If exists retrieve the task for the given key, if not receive an idle task
public subscript(task key: Key) -> Value {
self[key] ?? .idle()
}

public func hasValue(for key: Dictionary.Key) -> Bool {
Expand Down
Loading