Skip to content

Instantly share code, notes, and snippets.

@maximkrouk
Last active May 30, 2020 16:00
Show Gist options
  • Save maximkrouk/efb78acf29074c3a55a727474c00a933 to your computer and use it in GitHub Desktop.
Save maximkrouk/efb78acf29074c3a55a727474c00a933 to your computer and use it in GitHub Desktop.
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)) }
)
}
}
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