Search code examples
swiftreflectiondelegatesaop

Want to avoid copy-pasting method names for a Swift 2 delegate dispatcher


I’m trying to implement a dispatcher to notify multiple targets for a delegate pattern protocol.

What’s a better way of doing the following pattern, without copy-pasting for every method name? Reflection, aspect-oriented programming, meta-programming all come to mind:

public class AirBatteryClientDelegateDispatcher: AirBatteryClientDelegate {
    private var targets = [AirBatteryClientDelegate]()

    public func clientDidStartScan(client: AirBatteryClient) {
        for target in targets {
            target.clientDidStartScan?(client)
        }
    }

    . . .
}

For reference, I’m using the following protocol with a dozen more similar methods:

@objc public protocol AirBatteryClientDelegate: class {
    optional func clientDidStartScan(client: AirBatteryClient)
    optional func clientDidStopScan(client: AirBatteryClient)
    optional func clientDidUpdateState(client: AirBatteryClient)
    . . .
}

Solution

  • You will have to implement all the delegate methods explicitly, but you can at least reduce the amount of repetition by implementing them all the same way. Effectively, since you're using an Objective-C feature (optional protocol methods), you are writing this code in Objective-C:

    @objc protocol AirBatteryClientDelegate: NSObjectProtocol { // *
        optional func clientDidStartScan(client: AirBatteryClient)
        optional func clientDidStopScan(client: AirBatteryClient)
        optional func clientDidUpdateState(client: AirBatteryClient)
    }
    
    class AirBatteryClientDelegateDispatcher {
        private var targets = [AirBatteryClientDelegate]()
    
        private func call(f:String, client:AnyObject) {
            for target in targets {
                let f = f + ":" // work around Swift bug
                if target.respondsToSelector(Selector(f)) {
                    target.performSelector(Selector(f), withObject:client)
                }
            }
        }
    
        func clientDidStartScan(client: AirBatteryClient) {
            call(#function, client:client) // or __FUNCTION__ before Swift 2.2
        }
    
        // ... and the others are identical
    }
    

    However, it would be even better if you really were writing this code in Objective-C, which is made for this kind of dynamic forwarding. You'd be able to be even more elegant. But I won't describe how you'd do that, as it would take us too far afield from what you asked.