vendredi 1 avril 2016

how can I make this Swift event handler boilerplate more concise?

I've seen a pattern in Java that lets you implement a subset of a list of callbacks, in a type-safe way, inline with the class that uses the callbacks:

registerHandlers(new ClassWithNoOpMethods() {
    @override
    public void onFooEvent(FooEvent event) { ... }
    @override
    public void onBarEvent(BarEvent event) { ... }
}

All nice and type-safe. I'd like to do the same thing in Swift, but some googling didn't turn up any (IMHO) elegant solutions. So I came up with this:

let registrar = EventSource.getEventRegistrar()
registrar.onFooEvent = { event in doSomethingFoo(event) }
registrar.onBarEvent = { event in doSomethingBar(event) }
...
EventSource.removeEventCallbacks(registrar)

This works fine for consuming the events - its just the subset of events I'm interested in, it lets me define the code inline, etc etc.

However, when I actually implemented this, I got a lot of repeated, boilerplate, non-DRY code. It offends me, but I can't figure out a better way to implement the scheme shown above. I'd like to appeal to the Swift gods on StackOverflow to show me a more concise way to implement this.

Here is what it looks like now:

public class Phone {
    ...phone stuff...
    public class PhoneEventRegistrar {

        let phone : Phone

        init(phone : Phone) {
            self.phone = phone
        }

        public typealias OnErrorCallback = (PhoneErrorType, String) -> Void
        private var onErrorValue : OnErrorCallback?
        public var onError : OnErrorCallback {
            get { return onErrorValue != nil ? onErrorValue! : {_,_ in} }
            set {
                assert(onErrorValue == nil, "onError cannot be set twice")
                onErrorValue = newValue
            }
        }
        func invokeErrorCallback(type : PhoneErrorType, message : String) {
            if let onErrorValue = onErrorValue {
                onErrorValue(type, message)
            }
        }

        public typealias OnCallStateChangeCallback = (CallState) -> Void
        private var onCallStateChangeValue : OnCallStateChangeCallback?
        public var onCallStateChange : OnCallStateChangeCallback {
            get { return onCallStateChangeValue != nil ? onCallStateChangeValue! : {_ in} }
            set {
                assert(onCallStateChangeValue == nil, "onCallStateChange cannot be set twice")
                onCallStateChangeValue = newValue
            }
        }
        func invokeCallStateChangeCallback(state : CallState) {
            if let onCallStateChangeValue = onCallStateChangeValue {
                onCallStateChangeValue(state)
            }
        }

        // and the mostly-similar code shown twice above is repeated for
        // each possible callback
    }

    func invokeErrorCallbacks(type : PhoneErrorType, message : String) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        registrars.forEach({$0.invokeErrorCallback(type, message: message)})
    }

    func invokeCallStateChangeCallbacks(state : CallState) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        registrars.forEach({$0.invokeCallStateChangeCallback(state)})
    }

    // again, the mostly similar block of code shown twice above is
    // repeated for each possible callback

    private var registrars : [PhoneEventRegistrar] = []
    public func getPhoneEventRegistrar() -> PhoneEventRegistrar {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        let registrar = PhoneEventRegistrar(phone: self)
        registrars.append(registrar)
        return registrar
    }

    public func removeRegistrarCallbacks(registrar : PhoneEventRegistrar) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        assert(registrars.contains({$0 === registrar}), "cannot remove callbacks, no matching PhoneEventRegistrar found")
        registrars = registrars.filter({$0 !== registrar})
    }
}

Aucun commentaire:

Enregistrer un commentaire