Skip to content

Instantly share code, notes, and snippets.

@danielt1263
Created March 13, 2017 21:31
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save danielt1263/554e2ade0a726ee7f0ad041644493ff8 to your computer and use it in GitHub Desktop.
//
// 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