VIPER of many faces — Part 3: EventEmitters in Swift
…. continuing from Part 2.
You’ve probably wondered by now about the name of the demo project, presenter in previous chapter and yes, it did have a certain meaning and we are coming to it now.
Complex communications between the classes
We’ve mentioned this issue few times before and we said we understand our current demo is pretty much simple, almost too simple, like a VIPER textbook example:
Every event triggered on the views by the user is being captured by their View Controllers who handle them simply by calling Presenter and initiating new flows. Presenters handle the call either by creating new View Controllers or calling Interactors or anything else for that matter.
But what if the path from the View to Presenter is not that simple and short?
Cell got a CTA Button
CTA Button? So what? Connect it to
LastPaymentsViewControlleras you did with the other outlets and you have direct path to
Yes. Could work. But what if our cell is not a Prototype Cell in Interface Builder anymore? What if it resides in its own
.xibfile? Well, this is where it starts to get interesting. Namely, the owner of the
DashboardPaymentCelland that one gets instantiated by the Interface Builder at the runtime, added to Table View and doesn’t reference any other object in our graph.
How to propagate our event and its parameters back to the business logic? We can do it in many ways, let’s mention some:
- going hacky way and figure out from the
DashboardPaymentCellwhich Table View owns it and then go from there. Nasty way…
- we could pass an action delegate to the cell when we populate it and then handle it there, it could be our
- we could pass our
DashboardPresenterdirectly to the
Plenty of possibilities, but none fits perfectly or at least attractive enough.
If we pass Presenter in any form, then this would actually serve the purpose now. But what if we have a lot of such events, which we usually do? What if each of these events needs different handling? Well, in that case, our Presenter could get very cluttered, loaded with extensions conforming to protocols, each representing on specific event handling, being referenced by all too many objects, especially views, becoming Godfather to them all…
In many cases, we would create a separate event handling objects and our Presenter would suddenly lose its god name of being an object which responsibility is to handle presentation logic and not being some sort of event handling proxy. Although, the idea of such object is not bad at all… Imagine the object, something like Event Proxy, very lightweight, which would connect event creators with event handlers…
It’s not. Let’s make something like this:
So, what we have is:
- EventEmitter object which stores array of callback closures
- Subscribers(Handlers) subscribe to EventEmitter with a closure
- Emitter objects fire an event with the payload of simple dictionary which is then delivered anonymously through the closure to Subscribers
Events can be plenty!
Now, imagine that we add all kinds of analytics to our app. Sending events with the payloads to our data analytics endpoints. EventEmitters seem like an ideal tool for it.
Actually, we can put all kinds of events into the same payload corresponding to one user action and different subscribers simply take their own relevant information filtered by the key, if it exists. So, instead of calling multiple closures or multiple delegates from the object which received user action, we simply fire one payload to one EventEmitters and whoever is interest in particular part of the payload will handle it!.
Sounds almost like perfect! Hm… sounds very familiar, sounds almost like…..Notification??
But, but, but,… why not just (NS)NotificationCenter then?
Indeed, why not? It’s a clear example of Publish-Subscribe patterns and there is no coupling between the publisher and subscriber.
NotificationCenter is a global, which deliver a simple payload and that’t more or less it.
Well, I have to admit I was never a big fan of the approach. I remember once having to refactor one Mac OS app. It contained over 70 different notifications and you can imagine how hard it was for me to debug such an app, poised with race conditions amongst others.
That was also the time I realised NSNotifications are not always thread safe and this hadn’t really been resolved up until iOS 9.
But there are many more issues that have always bothered me
- Global character of
NSNotificationCentermakes it all too easy to subscribe wrong object to the wrong notification, which could easily cause crashes or other problems when object, subscribed to wrong notification got unexpected payload and it couldn’t be parsed as expected. Of course this smells also of unsafe coding, but the latter can be a fact all too many times
- Inspecting the stack on debugger between Subscriber and Publisher doesn’t tells us much, there’s no direct link between the threads
- Not easy to test, because
NSNotificationCenteris a global broker for event messages, making itself a middleman and to have it tested and since it’s a singleton, we can’t speak about unit test isolation case anymore. Mocking it would require code to change just to accommodate the mock, which is not a good pattern.
- Requires unsubscribing, otherwise you end up with memory leaks
How does then our own EventEmitter compares to this:
- It has a local, module character, all its scope is contained within the module
- Very easy to debug, you just simply put a single breakpoint on publishing function and this is it. We shall see that later in the code
- One place for all events, structurally within the enum, but we can split them into multiple
EventEmitterclasses, if we would like to. It would break up a single, overall view, but it still gives us a much better overview than leaving single publishing instantiations around our code, like in case of
- Payloads are simple collections, dictionaries within the closure, always passed by value, no race conditions possible
- Payload delivery, the emitting part in
EventEmitteris synchronised and its “complexity” contained within
- With main View Controller dismissed, all other objects, including EventEmitter are de-allocated, no fear of any memory leaks, the only thing we have to be careful about is weakly referencing
EventEmitterin all other objects but Presenter who owns it in our case.
- No need for any “unsubscribing”, because if the objects, who pass subscribing closure to the EventEmitter, will with the help of compiler capture their
[weak self]thus making this crash safe
If I look at the summary, I have no doubts about the winner…
The next step is of course the demo project code, which will be described in the next, last part.