Created
March 13, 2017 21:31
-
-
Save danielt1263/554e2ade0a726ee7f0ad041644493ff8 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
// | |
// Dispatcher.swift | |
// Flux | |
// | |
// Created by Daniel Tartaglia on 3/13/17. | |
// Copyright © 2017 Daniel Tartaglia. MIT License. | |
// | |
import Foundation | |
class Dispatcher<Payload> { | |
typealias DispatchToken = String | |
/// Registers a callback to be invoked with every dispatched payload. Returns a token that can be used with `waitFor()`. | |
func register(callback: @escaping (Payload) -> Void) -> DispatchToken { | |
let id = UUID().uuidString | |
callbacks[id] = callback | |
return id | |
} | |
/// Removes a callback based on its token. | |
func unregister(id: DispatchToken) { | |
precondition(callbacks[id] != nil, "Dispatcher.unregister(...): `\(id)` does not map to a registered callback.") | |
callbacks.removeValue(forKey: id) | |
} | |
/// Waits for the callbacks specified to be invoked before continuing execution of the current callback. This method should only be used by a callback in response to a dispatched payload. | |
func waitFor(ids: Array<DispatchToken>) { | |
precondition(isDispatching, "Dispatcher.waitFor(...): Must be invoked while dispatching.") | |
for id in ids { | |
if isPending[id] == true { | |
precondition(isHandled[id] == true, "Dispatcher.waitFor(...): Circular dependency detected while waiting for `\(id)`.") | |
continue | |
} | |
precondition(callbacks[id] != nil, "Dispatcher.waitFor(...): `\(id)` does not map to a registered callback.") | |
invokeCallback(id) | |
} | |
} | |
/// Dispatches a payload to all registered callbacks. | |
func dispatch(payload: Payload) { | |
precondition(!isDispatching, "Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.") | |
startDispatching(payload) | |
for id in callbacks.keys { | |
if isPending[id] != true { | |
invokeCallback(id) | |
} | |
} | |
stopDispatching() | |
} | |
private (set) var isDispatching: Bool = false | |
private var callbacks: [DispatchToken: (Payload) -> Void] = [:] | |
private var isPending: [DispatchToken: Bool] = [:] | |
private var isHandled: [DispatchToken: Bool] = [:] | |
private var pendingPayload: Payload? = nil | |
private func invokeCallback(_ id: DispatchToken) { | |
guard let callback = callbacks[id] else { fatalError() } | |
guard let pendingPayload = pendingPayload else { fatalError() } | |
isPending[id] = true | |
callback(pendingPayload) | |
isHandled[id] = true | |
} | |
private func startDispatching(_ payload: Payload) { | |
for id in callbacks.keys { | |
isPending[id] = false | |
isHandled[id] = false | |
} | |
pendingPayload = payload | |
isDispatching = true | |
} | |
private func stopDispatching() { | |
pendingPayload = nil | |
isDispatching = false | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment