Mimicking Cocoa’s NSNotificationCenter in AS3

I’ve been semi-covertly developing an Adobe AIR app that interfaces with Harvest.  At Archer, we use Harvest pretty extensively.

Like most software platforms, Harvest offers a public (RESTful) API that tinkerers can tap into at will. This allows for the independent creation of client-side widgets of all shapes and sizes, and generally aids the longevity of the service-driven app.  This Harvest client will essentially be ‘Harvest on steroids’, because I will be adding some additional functionality that I find lacking with the web component…functionality you can only really get with a rich app that can be broken out of the browser and run in the background like a good little daemon.

To do this, I decided that AIR will be the platform of choice for a few compelling reasons:

  1. I wanted to learn the platform, including the ins and outs of AS3 and FlashBuilder (still in Beta, but will eventually replace FlexBuilder)
  2. I like AIR’s portability across platforms
  3. I didn’t want to write this in Java

I’ve spent the better part of the last two years developing for the iPhone.  For those out there that don’t know, native iPhone apps are generally written in Objective-C (Cocoa), which is kind of a hybrid of ANSI-C and SmallTalk. For most developers in the ‘traditional’ world of OOP (C-family of languages), Cocoa can be confusing at first. The very concepts we were taught in our Comp Sci classes are altered slightly with Cocoa. It is probably analogous to a Spaniard hearing Poruguese for the first time.  Everything looks familiar…but it’s different.  That’s Cocoa.

Of all the things Cocoa offers, I think the NSNotificationCenter (from the Foundation framework) is definitely one of the best. It saves developers the frustration of writing low-level event handlers for everything that needs to use them.  In Cocoa, the NSNotificationCenter lives as a Singleton (sharedNotificationCenter), although you could spawn another one if you really need it.

I guess the biggest convenience that NSNotificationCenter provides in Cocoa development is the global messaging registry. Using it allows you to essentially create informal contracts between various objects in your code. These contracts are completely arbitrary as they are defined by you, the developer.

In my brief experience with Actionscript 3, Ive had to do a lot of things that call for asynchronous operations, both for performance as well as user experience. Traditionally in AS3, you would create a subclass of EventDispatcher to handle such events.  To the EventDispatcher, you can add event listeners like so:

function mousedOver(event:MouseEvent):void {
 //  do something to indicate the mouseover event
}

function mousedOut(myevent:MouseEvent):void {
 // do something to indicate the mouse out event
}

myDispatcher.addEventListener(MouseEvent.MOUSE_OVER, mousedOver);
myDispatcher.addEventListener(MouseEvent.MOUSE_OUT, mousedOut);

This is all well and good. For a quick and dirty script, this is probably the optimal solution. However, there are some limitations to this implementation. First of all, the myDispatcher object must be able to handle MouseEvent events. This enforces a tight coupling of the events to be captured and the objects that need to handle them. Again, for smaller applications, this is generally fine. For larger applications, the effort to manage these event dispatches grows quickly.

In Cocoa, UI-level objects have a similar mechanism for event handling by declaring and binding selectors, either programmatically or by creating IBOutlets in Interface Builder. Another event handling strategy in Cocoa is the use of Key-Value Observing (KVO), which instructs an object to spy on a property of another object. Then there’s the NSNotificationCenter.

In layman’s terms, the NSNotificationCenter acts as a switchboard. A listener object tells the switchboard ‘hey, I would like you to tell me when you intercept the message XYZ‘. A variation of this message that’s important here is ‘hey, I would like you to tell me when you intercept the message XYZ, but only when object A is the sender‘.  Unlike AS3′s EventDispatcher, the listener object does not need to be ‘hardwired’ to listen for a specific event type.  In AS3 event handling, an object that doesn’t know how to handle MouseEvent events wont do anything if they receive them. What’s more, even if the listener object could intercept MouseEvent events, it has no say who the sender could be. The runtime in AIR/Flash dictates when messages are received.  A NotificationCenter grants the developer that cherished 30,000-foot view of the state of at least one aspect of their code. If adopted globally, the NotificationCenter will be the only object receiving and delegating messages.  The receiver doesn’t necessarily need to know who the sender was.

Luckily, AS3 provides some key objects and functionality that make the implementation of a true NotificationCenter very straightforward. The first object is the good ol’ Dictionary. The Dictionary, again in layman’s terms, is a means to make objects point to other objects. These objects can be anything in AS3, including functions. This is one of Actionscript’s major strengths, in my opinion. The other AS3 goodie that makes a NotificationCenter possible is the ability to treat functions as objects.  Languages that do not do this tend to require the developer to perform some code gymnastics using reflection – a costly mess in even the most robust implementations. The last piece that makes the NotificationCenter possible is the rest operator (…).  This allows for the passing around of arbitrary data.

And now to the implementation

package com.archergroup.util
{
    import flash.utils.Dictionary;

    public class NotificationCenter
    {
        private static var _weakKeys:Boolean = true;
        private static var _instance:NotificationCenter = null;
        private static var _oneandonly:Boolean = true;

        private var _objectObservers:Dictionary = null;
        private var _eventObservers:Dictionary = null;

 /**
 * Constructor
 * Blows up if not called by the public static method
 */
    public function NotificationCenter()
    {
        if (_oneandonly) {
            throw new Error("The NotificationCenter must be called as a Singleton.  Use getInstance() instead.");
        } else {
            _objectObservers = new Dictionary(_weakKeys);
            _eventObservers = new Dictionary(_weakKeys);
        }
    }

 /**
 * Singleton creation and fetch method
 */
    public static function getInstance():NotificationCenter
    {
        if (null == _instance) {
            _oneandonly = false;
            _instance = new NotificationCenter();
            _oneandonly = true;
        }

        return _instance;
    }

This is just the setup and enforcement of the Singleton pattern. AS3 doesn’t allow private constructors, so a little sleight-of-hand is necessary. I am creating two internal Dictionaries – one for general notifications where the listener registers to receive the event no matter who sends it, and one messages specific to the notifier.

/**
 * Registers an object as an observer for an event.  If the notifier param is not set, the observer will listen for
 * all notifications of event.  If the notifier is set, the observer will only listen for event from the notifier
 *
 * @param String event            The notification to be observed
 * @param Object observer        The object listening for event
 * @param Function callback        The function invoked by the observer when the notification fires
 * @param Object notifier        The specific object that is being observed for event <OPTIONAL>
 */
public function addObserverForEvent(event:String, observer:Object, callback:Function, notifier:Object = null):void
{
    if (!notifier) {
        if (!_eventObservers[event]) {
            _eventObservers[event] = new Dictionary(_weakKeys);
        }

        var _eObserverDict:Dictionary = _eventObservers[event] as Dictionary;
        _eObserverDict[observer] = callback;
    } else {
        if (!_objectObservers[notifier]) {
            _objectObservers[notifier] = new Dictionary(_weakKeys);
        }
        var _oObserverDict:Dictionary = _objectObservers[notifier] as Dictionary;
        if (!_oObserverDict[event]) {
            _oObserverDict[event] = new Dictionary(_weakKeys);
        }
        var _oEventObserverDict:Dictionary = _oObserverDict[event] as Dictionary;
        _oEventObserverDict[observer] = callback;
    }
}

 /**
 * Removes an object as an observer for an event.  If the notifier param is set, this will only remove
 * the object as an observer for event fired by the notifier
 *
 * @param String event            The notification to be ignored
 * @param Object observer        The object that will be ignoring the event
 * @param Object notifier        The specific object that is being ignored for event <OPTIONAL>
 */
public function removeObserverForEvent(event:String, observer:Object, notifier:Object = null):void
{
    if (!notifier) {
        if (_eventObservers[event]) {
            var _eObserverDict:Dictionary = _eventObservers[event] as Dictionary;
            if (_eObserverDict[observer]) {
                delete _eObserverDict[observer];
            }
        }
    } else {
        if (_objectObservers[notifier]) {
            var _oObserverDict:Dictionary = _objectObservers[notifier] as Dictionary;
            if (_oObserverDict[event]) {
                var _oEventObserverDict:Dictionary = _oObserverDict[event] as Dictionary;
                if (_oEventObserverDict[observer]) {
                    delete _oEventObserverDict[observer];
                }
            }
        }
    }
}

The above snippet contains the methods that enable you to tell the NotificationCenter to start (or stop) listening for messages. In both cases, I’m doing a simple test for nullness before I go ahead and add or remove the relationship. In the case of the _objectObservers Dictionary, the notifiers become the reference keys, whereas the event names are the reference keys in _eventObservers. Each key in _objectObservers contains 1..n Dictionaries, one for each event being observed for this specific key. Those dictionaries in turn use the observer as the key and the callback function as the key’s value.

	/**
  * Broadcasts a notification to all listeners.  A notifier is required, but any object
  * generically registered as an observer for event will also be notified.  The observers
  * that are explicitly listening for event from the notifier will be notified FIRST.
  *
  * @param String event			The notification being broadcast
  * @param Object notifier		The object doing the broadcasting
  * @param Array args			Any additional parameters used for the broadcast
  */
public function postNotificationName(event:String, notifier:Object, ... args):void
{
	//	notify object observers first
	if (_objectObservers[notifier]) {
		var _oObserverDict:Dictionary = _objectObservers[notifier] as Dictionary;
		if (_oObserverDict[event]) {
			var _oEventObserverDict:Dictionary = _oObserverDict[event] as Dictionary;
			for (var o_observer:Object in _oEventObserverDict) {
				var callback:Function = _oEventObserverDict[o_observer] as Function;
				callback.apply(o_observer, args);
			}
		}
	}

	//	notify event observers next
	if (_eventObservers[event]) {
		var _eObserverDict:Dictionary = _eventObservers[event] as Dictionary;
		for (var ev_observer:Object in _eObserverDict) {
			var callback:Function = _eObserverDict[ev_observer] as Function;
			callback.apply(ev_observer, args);
		}
	}
}

The notifications are sent with optional arguments (the rest operator) from the caller. By default, I am notifying object observers before anyone else, but this is arbitrary. If the notifier is a key in _objectObservers, and the event is a key in the subsequent sub-Dictionary, then call all functions stored within. If you have the _weak_keys static variable set to false, you will avoid any null pointer exceptions. This is an advantage over the NSNotificationCenter in Cocoa, where you must remove objects from the notification queue when you deallocate them.

And now a brief usage example:

	public function whoami(... args):void
{
        var notifier:NotificationCenter = NotificationCenter.getInstance();
        notifier.addObserverForEvent(HarvestAPI.SERVICE_RESPONSE_AUTHENTICATE, this, _handleServiceAuthenticate);
	var tts:TimeTrackerService = new TimeTrackerService(this);
	tts.whoAmI(args);
}

//    ...
private function _handleServiceAuthenticate(... args):void
{
	var notifier:NotificationCenter = NotificationCenter.getInstance();
	notifier.removeObserverForEvent(HarvestAPI.SERVICE_RESPONSE_AUTHENTICATE, this);

	//	the variable args should consist of at most one element, an instance of User
	var arr:Array = args as Array;
	if (null != arr && arr.length > 0) {
		var user:User = arr[0] as User;
		var prefs:Userprefs = Userprefs.getInstance();
		prefs.saveWithUser(user);
	}

	//	the user has properly authenticated.
	notifier.postNotificationName(HarvestAPI.CONTROLLER_AUTHENTICATION_OK, this, args);
}

This is an actual excerpt from the Harvest app. I am basically calling an authentication service in the Harvest API. On line 4 above, I tell the NotificationCenter to start listening for HarvestAPI.SERVICE_RESPONSE_AUTHENTICATE from anyone who broadcasts it, and then handle the notification with _handleServiceAuthenticate. Since this is a network call, everything must obviously be asynchronous. You’ll notice that in _handleServiceAuthenticate, an object is expected. If there is an incoming object, it will always be nested in an Array. This particular code then triggers another notification to a controller in an MVC hierarchy.

I’ve been coding against this NotificationCenter for a little while now, and I am really starting to appreciate it. I’m beginning to really enjoy Actionscript 3. The language has definitely grown up.

Leave a Reply

Close Modal

Contact Archer

Close Modal

We know you're still using an older version of Internet Explorer. Do you know how much mind-blowing stuff you're missing because of this?

Save yourself. Upgrade now.
We'll help you...