Publish-Subscribe in Swift

For one of my current projects, I had to implement an event-based messaging system. The first implementation boiled down to a bunch of NSNotificiation and addObserver. But this quickly became messy and the de-coupling was that strong, that debugging would be much less fun (for more on this topic, see also Why NSNotificationCenter is Bad, which I did not believe in the very beginning, but now there is some truth in it).

So, here we go and the first thing to implement was a message broker which acts as centralized system for subscriptions and sending messages to the subscribers. I designed this as a singleton. Yes, they are considered being bad, but in this case it makes perfect sense IMO:

private let _messageBroker = MessageBroker()  
typealias MessageKey = String

// Protocols

protocol Message    { func messageKey() -> MessageKey }  
protocol Subscriber { func receive(#message: Message) }


// Broker Implementation

class MessageBroker : NSObject  
{

    class var sharedMessageBroker : MessageBroker
    {
        return _messageBroker
    }

    private var subscribers = Dictionary<MessageKey, Array<Subscriber>>()

    func subscribe(subscriber: Subscriber, messageKey: MessageKey)
    {
        if subscribers[messageKey] == nil
        {
            subscribers[messageKey] = []
        }
        subscribers[messageKey]!.append(subscriber)
    }

    func publish(#message: Message)
    {
        if let subscribers = subscribers[message.messageKey()]
        {
            for subscriber in subscribers
            {
                subscriber.receive(message: message)
            }
        }
    }

}

Probably the most notable thing here is typealias MessageKey = String (surprise!). Everything else is straightforward.

So, why MessageKey? This is, because the pub-sub shall be able to use an enum in order to apply a switch on the received message. But, while the enum can conform to a protocol, you cannot add it to the Dictionary, which holds the receivers wrt a message type. Furthermore, it is currently not possible in Swift to define a protocol as Hashable for using it as key in a dictionary, e.g. Usage of protocols as array types and function parameters in swift. Yes, one could use a class and make this one hashable, but I feel this is getting nasty.

Q: How does a bunch of messages look?

enum SomeMessages : Message  
{
    case Foo, case Bar

    static let FooType = "Foo"
    static let BarType = "Bar"

    func messageKey() -> MessageKey
    {
        switch self
        {
            case .Foo : return SomeMessage.FooType
            case .Bar : return SomeMessage.BarType
        }
    }
}

Q: How do I send a message?

MessageBroker.sharedMessageBroker.publish(message: SomeMessage.Foo)  

Q: How do I subscribe?

class A : Receiver  
{
    init()
    {
        MessageBroker.sharedMessageBroker.subscribe(self, messageKey: SomeMessage.FooType)
    }

    func receive(#message: Message)
    {
        if let message = message as? SomeMessage
        {
            switch message
            {
                case .Foo : println("It is a Foo!")
            }
        }
    }
}

Q: Why are you telling this story?

Because, I was looking around for something which also provides me the ability to trigger a callback (i.e. call it synchronous message), and this is really painful when relying on NSNotification. With the code above you can define e.g. the following message type:

enum SyncMessage  
{
    case Yay(callback: () -> Void)
}

In the publisher:

let message = SyncMessage.Yay({ println("Callback, baby!") })  
MessageBroker.sharedMessageBroker.publish(message: message)  

And in the receiver:

switch message  
{
    case .Yay(let callback) : println("Yay, callbacks!"); callback()
}

Boy, how I love Swift!