Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the swift way to send the same function call to multiple objects

Tags:

swift

In objective-c we have a -forwardInvocation: that can be used like this:

-(void)forwardInvocation:(NSInvocation*) anInvocation{
    BOOL didForward = NO;
    //iterate over our proxied objects
    for(id proxyObject in self.proxyObjects){
        //invoke the with the proxy object if it can handle the selector
        if ([proxyObject respondsToSelector:[anInvocation selector]]){
            didForward = YES;
            [anInvocation invokeWithTarget: proxyObject];
        }
    }
    //if we did not forward the invocation, then call super
    if(!didForward){
        [super forwardInvocation: anInvocation];
    }
}

This is helpful when you have a group of concrete classes that all need the same messages. For instance if you are implementing multiple analytics platforms, each of which need the same messages, but will handle them in different manners.

Lets do this in swift given what we know about the language. This starts out straightforward:

func doSomething1(){
    for proxyObject in proxyObjects{
        proxyObject.doSomething1()
    }
}

But then gets repetitive:

func doSomething2(){
    for proxyObject in proxyObjects{
        proxyObject.doSomething2()
    }
}

func doSomething3(){
    for proxyObject in proxyObjects{
        proxyObject.doSomething3()
    }
}


func doSomething4(){
    for proxyObject in proxyObjects{
        proxyObject.doSomething4()
    }
}

....And on and on

I know that I can use NSObject in swift, but thats just mixing in objective-c where we need it. What is a more effective, and less verbose way to handle this in pure swift?

like image 578
Saltymule Avatar asked Oct 19 '22 04:10

Saltymule


1 Answers

Swift was built to stay away from this very design pattern. There's no dynamic messaging like ObjC. Every method must be known at compile time. There's no true replacement in pure Swift. However we can emulate (to a limited extent) what the ObjC runtime does by using Swift's closures:

typealias MyAction = () -> Void
enum ValidActions  {
    case Hello
    case Goodbye
}

protocol MyProxyProtocol {
    var actions : Dictionary<ValidActions, MyAction> { get }
}

private class Concrete1 : MyProxyProtocol {
    var actions = Dictionary<ValidActions, MyAction>()

    init() {
        self.actions[.Hello] = helloWorld
        self.actions[.Goodbye] = goodbyeWorld
    }

    func helloWorld() -> Void {
        print("Hello world from concrete 1")
    }

    func goodbyeWorld() -> Void {
        print("Goodbye world from concrete 1")
    }
}

private class Concrete2 : MyProxyProtocol {
    var actions = Dictionary<ValidActions, MyAction>()

    init() {
        self.actions[.Hello] = hello
    }

    func hello() -> Void {
        print("Hi from concrete 2")
    }
}

public class AbstractClass {
    var proxyObjects = [MyProxyProtocol]()

    init() {
        self.proxyObjects.append(Concrete1())
        self.proxyObjects.append(Concrete2())
    }

    func performAction(action : ValidActions) {
        for proxy in self.proxyObjects {
            if let f = proxy.actions[action] {
                f()
            }
        }
    }
}

let x = AbstractClass()
x.performAction(.Hello)    // Both concrete classes will do this
x.performAction(.Goodbye)  // Only the first one will do this

Each concrete class exposes the actions it can handle via the actions dictionary. The functions that handle them as stored as closures inside.

like image 68
Code Different Avatar answered Nov 15 '22 06:11

Code Different