如何使这个Swift事件处理程序样板更简洁?

时间:2016-04-02 02:57:32

标签: swift events design-patterns

我在Java中看到过一种模式,它允许您以类型安全的方式实现回调列表的子集,与使用回调的类内联:

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

一切都很好,类型安全。我想在Swift中做同样的事情,但一些谷歌搜索并没有出现任何(恕我直言)优雅的解决方案。所以我想出了这个:

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

这适用于消费事件 - 它只是我感兴趣的事件的子集,它允许我定义代码内联等等。

然而,当我实际实现这个时,我得到了很多重复的样板,非DRY代码。它冒犯了我,但我无法找到更好的方法来实现上面显示的方案。我想请求StackOverflow上的Swift众神向我展示一种更简洁的方法来实现它。

现在看来是这样的:

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})
    }
}

如果人们看到对于活动消费者具有相同可用性优势的替代实施方式,我也很乐意看到这些。以下是我曾经想过或尝试过的其他一些选择:

  • 实现定义回调的协议。让事件使用者实现协议并将其注册为回调。否定:要求实施所有方法 - 强制进行过多的无操作回调。
  • 如上所述,但使用默认的空白实现扩展协议。否定:仍然需要事件使用者为回调实现额外的类。此外,缺乏协议扩展的动态调度意味着注册协议将强制生成的事件转到no-op实现而不是真实的事件处理程序。
  • 将使用者类设置为事件生产者的委托。否定:仍然必须实现无操作回调。我每个活动制作人只见过一个代表,我需要几个代表。下面的@Darko评论促使我进一步考虑这个选项。

1 个答案:

答案 0 :(得分:2)

NSNotificationCenter

您的问题有多种解决方案。一种可能是简单地使用NSNotificationCenter而不是实现自己的事件机制。

使用NSNotificationCenter,您可以注册活动:

NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(MyClass.myMethod),
name: "com.mycompany.myEvent1",
object: userData)

从任何地方发送事件:

let userData: [NSObject: AnyObject] = [anyObject: anyDataToSend]
NSNotificationCenter.defaultCenter.postNotificationName("com.mycompany.myEvent1", object: userData)

你会在SO和Google上找到很多关于NSNotificationCenter的教程。

使用可选协议委派

另一种方法是使用委托模式。创建协议并将其标记为@objc,这样协议方法可以标记为“可选”。

@objc protocol MyEventProtocol: class {
  optional func foo() -> Bool
  func bar()
}

现在每个符合此协议的对象都必须实现bar(),但可以实现foo()。

class MyEventReceiver: MyEventProtocol {
    func bar() { // do something }
}

然后您的事件发件人类可以执行以下操作:

class MyEventSender {
     weak var delegate: MyEventProtocol? // the name "delegate" is just convention, you can use any other name

     init(receiver: MyEventReceiver) {
         self.delegate = receiver
     }

     func sendEventToReceiver() {
         if let delegate = self.delegate {
             delegate.func() // guaranteed by the protocol
             delegate.foo?() // foo is marked as optional, so you have to check it with ?. If foo is implemented on the receiver it will be called, otherwise not.
         }
     }
}

这是基本原则。通过这种方式,您可以为所有可能的事件定义一个协议,但协议的实现者只需要实现未标记为optional的方法。他们是“必需的”。

使用默认方法的协议扩展

第三种方法是使用默认方法创建协议扩展。

protocol MyEventProtocol: {
  func bar()
}

extension MyEventProtocol {
    func foo() -> Bool {
        return true
    }
}

然后MyEventProtocol的实现者不必实现foo(),因为已经有了实现。 (这是一种带有可选方法的“伪造”@objc协议)如果您为此解决方案添加一些通用机制,您还可以防止重复代码重复。 (协议中的泛型是在Swift 2.2中使用associatedtype完成的,请参阅其他教程)