Last active
May 30, 2020 16:00
-
-
Save maximkrouk/efb78acf29074c3a55a727474c00a933 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
typealias Reducer<State, Action> = (inout State, Action) -> Void | |
final class Store<State, Action>: ObservableObject { | |
typealias Effect = AnyPublisher<Action, Never> | |
@Published private(set) var state: State | |
private let lock: NSLock = .init() | |
private let reducer: Reducer<State, Action> | |
private let cancellables = CancellablesManager() | |
init(state: State, reducer: @escaping Reducer<State, Action>) { | |
self.state = state | |
self.reducer = reducer | |
} | |
func send(_ action: Action) { | |
print(log(state, action)) | |
reducer(&state, action) | |
} | |
func send(_ effect: Effect) { | |
effect | |
.receive(on: RunLoop.main) | |
.sink(using: cancellables, | |
onCompletion: { _ in }, | |
onSuccess: send) | |
} | |
func log(_ state: State, _ action: Action) -> String { | |
let logId = UUID() | |
return | |
"#startlog {\(logId.uuidString)}:\n" + | |
"> Date : \(Date())\n" + | |
"> State : \(state)\n" + | |
"> Action : \(action)\n" + | |
"#endlog {\(logId.uuidString)}." | |
} | |
} | |
extension Store { | |
func binding<Value>( | |
for keyPath: KeyPath<State, Value>, | |
_ action: @escaping (Value) -> Action | |
) -> Binding<Value> { | |
Binding<Value>( | |
get: { self.state[keyPath: keyPath] }, | |
set: { self.send(action($0)) } | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
extension AnyCancellable { | |
public func store(in manager: CancellablesManager) { | |
manager.store(self) | |
} | |
} | |
public class CancellablesManager { | |
private let lock = NSLock() | |
public typealias Storage = Set<AnyCancellable> | |
private var cancellables: Storage = [] | |
public func store(_ element: AnyCancellable?, _ precondition: Bool = true) { | |
lock.execute { | |
guard precondition, let element = element else { return } | |
self.cancellables.insert(element) | |
print("i", cancellables.map { $0.hashValue }) | |
} | |
} | |
public func release(_ element: AnyCancellable?, completion: () -> Void = {}) { | |
lock.execute { | |
guard let element = element else { return } | |
self.cancellables.remove(element) | |
print("r", cancellables.map { $0.hashValue }) | |
completion() | |
} | |
} | |
} | |
extension Publisher { | |
public func sink(using manager: CancellablesManager, | |
onCompletion: @escaping (Subscribers.Completion<Self.Failure>) -> Void, | |
onSuccess: @escaping (Self.Output) -> Void) { | |
var cancellable: AnyCancellable? | |
var didComplete = false | |
cancellable = handleEvents(receiveCancel: { manager.release(cancellable) }) | |
.sink( | |
receiveCompletion: { [weak manager] completion in | |
manager?.release(cancellable) { didComplete = true } | |
onCompletion(completion) | |
}, | |
receiveValue: { [weak manager] value in | |
manager?.release(cancellable) { didComplete = true } | |
onSuccess(value) | |
}) | |
if let cancellable = cancellable { | |
manager.store(cancellable, !didComplete) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment