Skip to content

Instantly share code, notes, and snippets.

@maximkrouk
Last active May 30, 2020 15:25
Show Gist options
  • Save maximkrouk/c804a7df4af93d056ba5b9e36565d3f6 to your computer and use it in GitHub Desktop.
Save maximkrouk/c804a7df4af93d056ba5b9e36565d3f6 to your computer and use it in GitHub Desktop.
public class CancellablesManager {
private let lock = NSLock()
public typealias Storage = Set<AnyCancellable>
private var cancellables: Storage = []
func store(_ element: AnyCancellable?, _ precondition: Bool = true) {
lock.execute {
guard precondition, let element = element else { return }
cancellables.insert(element)
print("i", cancellables.map { $0.hashValue })
}
}
func release(_ element: AnyCancellable?, completion: () -> Void = {}) {
lock.execute {
guard let element = element else { return }
cancellables.remove(element)
completion()
}
}
}
extension AnyCancellable {
func store(in manager: CancellablesManager) {
manager.store(self)
}
}
extension Publisher {
@discardableResult
public func sink(using manager: CancellablesManager,
onCompletion: @escaping (Subscribers.Completion<Self.Failure>) -> Void,
onSuccess: @escaping (Self.Output) -> Void) -> AnyCancellable {
var cancellable: AnyCancellable?
var didComplete = false
let release: () -> Void = { [weak manager] in
manager?.release(cancellable) { didComplete = true }
}
cancellable = handleEvents(receiveCancel: release)
.sink(
receiveCompletion: { completion in
release()
onCompletion(completion)
},
receiveValue: { value in
release()
onSuccess(value)
})
manager.store(cancellable, !didComplete)
return cancellable!
}
}
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 cancellablesManager = CancellablesManager()
init(state: State, reducer: @escaping Reducer<State, Action>) {
self.state = state
self.reducer = reducer
}
func send(_ effect: Effect) {
effect
.receive(on: RunLoop.main)
.sink(using: cancellablesManager,
onCompletion: executeNothing(),
onSuccess: send)
}
func send(_ action: Action) {
lock.execute {
print(log(state, action))
reducer(&state, action)
}
}
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> {
.init(get: { self.state[keyPath: keyPath] }, set: { self.send(action($0)) })
}
}
extension NSLock {
func store<T>(_ value: T, in object: inout T) {
mutate(&object, with: { $0 = value })
}
func mutate<T: AnyObject>(_ object: T, with closure: (T) -> Void) {
execute { closure(object) }
}
func mutate<T>(_ object: inout T, with closure: (inout T) -> Void) {
execute { closure(&object) }
}
@discardableResult
func execute<T>(code closure: () -> T) -> T {
lock()
defer { unlock() }
return closure()
}
}
@maximkrouk
Copy link
Author

It would be nice to have a readme/docs in Merge 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment