Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift : Create a multi-function multicast delegate

Tags:

ios

swift

I'm wanting to use a multicast delegate to inform multiple objects when things change. The tutorials I've read that explain this, have a protocol that only has one function that is called directly on the array of delegates. That works fine when there is only one function defined. My Protocol has 6 functions. I want to avoid creating 6 separate functions and reuse a single function that can be applied to my array of delegates.

Quick example: (I understand this is none working, but I just want to get my idea across.

protocol MyProtocol {
 func method1()
 func method2()
 func method3()
}


class TestClass {
  var delegates = [MyProtocol]()

  func invokeDelegates(delegateMethod: () -> ()) {
    for delegate in delegates {
      delegate.delegateMethod()
    }
  }
}

The obvious problem is the compiler complains that "delegateMethod" isn't defined in the original protocol. Is there a way that I cast the method as being part of MyProtocol and the compiler will trust me?

Is this even possible?

like image 502
DookieMan Avatar asked Jan 09 '17 16:01

DookieMan


People also ask

What is multicast delegate Swift?

Save. Multicast Delegate is a generic wrapper around yet another delegate protocol, that means it create one-to-many delegate relationships. This allows providing an object with an array of delegates.

What is multi cast delegate?

The multicast delegate contains a list of the assigned delegates. When the multicast delegate is called, it invokes the delegates in the list, in order. Only delegates of the same type can be combined. The - operator can be used to remove a component delegate from a multicast delegate.

Which operators are used for multicast delegates?

In Multicasting, Delegates can be combined and when you call a delegate, a whole list of methods is called. All methods are called in FIFO (First in First Out) order. + or += Operator is used for adding methods to delegates. – or -= Operator is used for removing methods from the delegates list.


2 Answers

Here is a gist of an Multicast Delegate pattern that I use in my projects. It also prevents from having strong reference cycles (memory leaks). WeakWrapper handles this.

Ok. In some of the solutions I see mistakes (strong retain cycles, race conditions, ...)

Here is what I combine based on 1 day research. For the stack of delegates I used NSHashTable, so all the delegates are having weak reference.

class MulticastDelegate <T> {
  private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()

  func add(delegate: T) {
    delegates.add(delegate as AnyObject)
  }

  func remove(delegate: T) {
    for oneDelegate in delegates.allObjects.reversed() {
      if oneDelegate === delegate as AnyObject {
        delegates.remove(oneDelegate)
      }
    }
  }

  func invoke(invocation: (T) -> ()) {
    for delegate in delegates.allObjects.reversed() {
      invocation(delegate as! T)
    }
  }
}

func += <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
  left.add(delegate: right)
}

func -= <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
  left.remove(delegate: right)
}



How to set delegate:

object.delegates.add(delegate: self)



How to execute function on the delegates: instead of

delegate?.delegateFunction

you use

delegates.invoke(invocation: { $0.delegateFunction })
like image 144
Klemen Avatar answered Dec 17 '22 04:12

Klemen


You need to change the signature of invokeDelegates to take a closure of type (MyProtocol) -> (), and then you need to pass each delegate to the closure.

protocol MyProtocol {
    func method1()
    func method2()
    func method3()
}

class TestClass {
    var delegates = [MyProtocol]()

    func invokeDelegates(delegateMethod: (MyProtocol) -> ()) {
        for delegate in delegates {
            delegateMethod(delegate)
        }
    }
}

The closure should just invoke the appropriate delegate method on its argument. Swift can infer the argument and return types of the closure, and you can use the shorthand $0 to refer to the argument, so the closure can be quite short:

let tester = TestClass()
tester.invokeDelegates(delegateMethod: { $0.method1() })

On the other hand, you could just use Collection.forEach directly on the delegates array (if it's accessible) and skip the invokeDelegates method:

tester.delegates.forEach { $0.method1() }
like image 26
rob mayoff Avatar answered Dec 17 '22 03:12

rob mayoff