Swift Language Multicast Delegate

csotiriou picture csotiriou · Nov 16, 2014 · Viewed 7.2k times · Source

I am trying to implement the multicast delegate functionality in Swift. In Objective C, we have this excellent implementation

https://github.com/robbiehanson/XMPPFramework/blob/master/Utilities/GCDMulticastDelegate.m

And I have just created this basic functionality:

protocol MyProtocol : class{
    func testString()-> String;
}

class MulticastDelegateNode <T:AnyObject> {
    weak var delegate : T?

    init(object : T){
        self.delegate = object;
    }
}

class MulticastDelegate <T:AnyObject> {
    var delegates = Array<MulticastDelegateNode<T>>()


    func addDelegate(delegate : T){
        var newNode = MulticastDelegateNode(object : delegate);
        delegates.append(newNode);
    }

    func removeDelegate(delegate : AnyObject){
        self.delegates = self.delegates.filter({ (node : MulticastDelegateNode) -> Bool in
            return node.delegate !== delegate;
        });
    }
}

class OP {
    var delegate  = MulticastDelegate<MyProtocol>();

    func process(){
        //...
        //make actions

        //notify the objects!
    }

}

My problem is that it seems I cannot figure out a way to do this:

delegate.testString()

In order to give the command 'testString()' to all delegates that are in the nodes. Can anyone help me with this?

Answer

mixel picture mixel · Nov 11, 2016

Swift 3 implementation:

class MulticastDelegate<T> {
    private var delegates = [Weak]()

    func add(_ delegate: T) {
        if Mirror(reflecting: delegate).subjectType is AnyClass {
            delegates.append(Weak(value: delegate as AnyObject))
        } else {
            fatalError("MulticastDelegate does not support value types")
        }
    }

    func remove(_ delegate: T) {
        if type(of: delegate).self is AnyClass {
            delegates.remove(Weak(value: delegate as AnyObject))
        }
    }

    func invoke(_ invocation: (T) -> ()) {
        for (index, delegate) in delegates.enumerated() {
            if let delegate = delegate.value {
                invocation(delegate as! T)
            } else {
                delegates.remove(at: index)
            }
        }
    }
}

private class Weak: Equatable {
    weak var value: AnyObject?

    init(value: AnyObject) {
        self.value = value
    }
}

private func ==(lhs: Weak, rhs: Weak) -> Bool {
    return lhs.value === rhs.value
}

extension RangeReplaceableCollection where Iterator.Element : Equatable {
    @discardableResult
    mutating func remove(_ element : Iterator.Element) -> Iterator.Element? {
        if let index = self.index(of: element) {
            return self.remove(at: index)
        }
        return nil
    }
}

You can test it with:

protocol SomeDelegate: class {
    func onSomeEvent()
}

class SomeDelegateImpl: SomeDelegate {
    let value: Int

    init(value: Int) {
        self.value = value
    }

    func onSomeEvent() {
        print("Invoking delegate \(value)")
    }
}

let multicastDelegate = MulticastDelegate<SomeDelegate>()

func testInvoke() {
    multicastDelegate.invoke {
        $0.onSomeEvent()
    }
}

print("Adding first delegate.")

let delegate1 = SomeDelegateImpl(value: 1)

multicastDelegate.add(delegate1)

testInvoke()

let delegate2 = SomeDelegateImpl(value: 2)

print("Adding second delegate.")

multicastDelegate.add(delegate2)

testInvoke()

print("Removing first delegate.")
multicastDelegate.remove(delegate1)

testInvoke()

print("Adding third delegate.")

({
    let delegate3 = SomeDelegateImpl(value: 3)
    multicastDelegate.add(delegate3)
    testInvoke()
})()

print("Third delegate is deallocated by ARC.")

testInvoke()

It prints:

Adding first delegate.
Invoking delegate 1.
Adding second delegate.
Invoking delegate 1.
Invoking delegate 2.
Removing first delegate.
Invoking delegate 2.
Adding third delegate.
Invoking delegate 2.
Invoking delegate 3.
Third delegate is deallocated by ARC.
Invoking delegate 2.

Based on this blog post.