Skip to content

Instantly share code, notes, and snippets.

@ncreated
Created June 14, 2018 16:37
Show Gist options
  • Save ncreated/5e3edaab9bf8af40016b14a9d538afde to your computer and use it in GitHub Desktop.
Save ncreated/5e3edaab9bf8af40016b14a9d538afde to your computer and use it in GitHub Desktop.
Container for storing many delegates and calling an operation on all at once. Uses weak references to store delegates.
import Foundation
/**
Container for storing many delegates and calling an operation on all at once.
Uses weak references to store delegates.
*/
public final class MulticastDelegate<T> {
private var delegates: [WeakDelegate] = []
public func add(delegate: T) {
assert(Thread.isMainThread, "`MulticastDelegate` is not thread safe")
let weakified = WeakDelegate(delegate as AnyObject)
delegates.append(weakified)
}
public func remove(delegate: T) {
assert(Thread.isMainThread, "`MulticastDelegate` is not thread safe")
cleanDeallocatedDelegates()
if let index = delegates.index(where: { $0.delegate === delegate as AnyObject }){
delegates.remove(at: index)
}
}
public func call(_ block: (T) -> ()) {
assert(Thread.isMainThread, "`MulticastDelegate` is not thread safe")
cleanDeallocatedDelegates()
let existingDelegates = delegates.compactMap { $0.delegate as? T }
existingDelegates.forEach { block($0) }
}
private func cleanDeallocatedDelegates() {
delegates = delegates.filter { $0.delegate !== nil }
}
}
private class WeakDelegate {
weak var delegate: AnyObject?
init(_ delegate: AnyObject) {
self.delegate = delegate
}
}
import XCTest
@testable import ...
private protocol DispatcherDelegate: class {
func didDispatch()
}
private class Listener: DispatcherDelegate {
var didDispatch_callsCount = 0
func didDispatch() {
didDispatch_callsCount += 1
}
}
class MulticastDelegateTests: XCTestCase {
func testCallingSingleDelegate() {
let delegates = MulticastDelegate<DispatcherDelegate>()
let listener = Listener()
delegates.add(delegate: listener)
delegates.call { $0.didDispatch() }
XCTAssertEqual(listener.didDispatch_callsCount, 1)
}
func testAddingDelegates() {
let delegates = MulticastDelegate<DispatcherDelegate>()
let listener1 = Listener()
let listener2 = Listener()
let listener3 = Listener()
delegates.add(delegate: listener1)
delegates.add(delegate: listener2)
delegates.add(delegate: listener3)
delegates.call { $0.didDispatch() }
XCTAssertEqual(listener1.didDispatch_callsCount, 1)
XCTAssertEqual(listener2.didDispatch_callsCount, 1)
XCTAssertEqual(listener3.didDispatch_callsCount, 1)
}
func testRemovingDelegates() {
let delegates = MulticastDelegate<DispatcherDelegate>()
let listener1 = Listener()
let listener2 = Listener()
let listener3 = Listener()
delegates.add(delegate: listener1)
delegates.add(delegate: listener2)
delegates.add(delegate: listener3)
delegates.call { $0.didDispatch() }
delegates.remove(delegate: listener3)
delegates.call { $0.didDispatch() }
delegates.remove(delegate: listener2)
delegates.call { $0.didDispatch() }
XCTAssertEqual(listener1.didDispatch_callsCount, 3)
XCTAssertEqual(listener2.didDispatch_callsCount, 2)
XCTAssertEqual(listener3.didDispatch_callsCount, 1)
}
func testKeepingWeakReferences() {
let delegates = MulticastDelegate<DispatcherDelegate>()
let listener1 = Listener()
delegates.add(delegate: listener1)
autoreleasepool {
let listener2 = Listener()
delegates.add(delegate: listener2)
}
var numberOfCallsMade = 0
delegates.call { _ in numberOfCallsMade += 1 }
XCTAssertEqual(numberOfCallsMade, 1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment