Selective removeAll() addition to as3-signals

22 09 2011

This post and code is related to my fork of as3-signals. The fork is from the latest version of as3-signals 0.9-BETA.

Summary of additions

  • ISlot.applyTo(value:*):void
  • ISlot.doesApply(value:*):Boolean
  • SlotList.filterNotAppliesTo(value:*):SlotList
  • SlotList.findNotAppliesTo(value:*):SlotList

Summary of modifications

  • IOnceSignal.removeAll(appliesTo:* = null):void

Details

This backwards compatible addition to as3-signals chiefly allows the removal of all listeners for a given instance or class type. It keeps the convenience of the removeAll function but provides a mechanism to prevent removal of listeners not bound to the current instance or class.

The removeAll() method allows for an optional parameter which will then removeAll slots that applyTo the given parameter, if any.

Essentially, this allows us to asynchronously clean up after a class or instance without affecting any listeners from other classes and without having to define our anonymous functions or closures.

Reasoning

A signal – as a promise – may be reused across instances and classes. For example, a service that is managed by an IOC container may cache signal calls, or a model may use signals to notify of changes. When the signal is shared across various types (either directly via the IOC container, or indirectly cached within an instance) we don’t want to removeAll() lest we remove functional code in other classes (that may well have been written by other developers).

Example

Update Sept 23: Changed the example to use Robotlegs and Mediation

Let’s look at an example to follow this through. This example below illustrates usage when marshalling updates of a model to a view (using covariant mediation to an interface) via a Mediator. Chiefly, the problem arises when an asynchronous cleanup method is called (in this case `onRemove()`), it needs to remove all of the listeners applied to by this instance but not remove listeners that may be used elsewhere.

public class LogModel
{
	protected var updateSignal:ISignal;
	protected var logs:Vector.<LogItem>;
	
	public function get update():ISignal
	{
		return updateSignal ||= new Signal();
	}
	
	public function set logs(collection:Vector.<LogItem>):void
	{
		logs = collection;
		update.dispatch(logs);
	}
}

public class SomethingMediator extends Mediator
{
     [Inject]
     public var model:LogModel;
	 
	 [Inject]
	 public var view:IDoesSomething;
	
     override public function onRegister():void
     {
        model.update.add(function(collection:Vector.<LogItem>):void
		{
			view.logs = collection;
		}).applyTo(this);
     }
  
     override public function onRemove():void
     {
          model.update.removeAll(this); //removes all listeners applied to this instance only
     }
}

Usage (pseudo-code):

//setup values
const logModel:LogModel = new LogModel();
injector.mapValue(LogModel, logModel);

//map interfaces to a mediator 
mediatorMap.mapMediator(IDoesSomething, SomethingMediator);
const viewA:IDoesSomething = new DoesSomething();
const viewB:IDoesSomething = new DoesSomething();

//register mediators for our views (creates two instances of SomethingMediator)
mediatorMap.registerMediators(viewA);
mediatorMap.registerMediators(viewB);

trace(logModel.update.numListeners); //2 - one listener from viewA.onRegister() and one from viewB.onRegister()

mediatorMap.removeMediators(viewA);

trace(logModel.update.numListeners); //1 - one listener from viewB.onRegister()

mediatorMap.removeMediators(viewB);

trace(logModel.update.numListeners); //0
About these ads

Actions

Information

5 responses

22 09 2011
Robert Penner

In this example, shouldn’t you use addOnce()?

22 09 2011
Justin J. Moses

Robert,

I concede it’s not the best example, but I wanted to keep it brief.

However, addOnce() only removes the listener once dispatched, so if then the containing “Thing” instance was disposed before the signal dispatched we’d still have listeners waiting for something that should have been disposed.

Justin

22 09 2011
Justin J. Moses

Also, if I used addOnce(), readers may have been confused why I’d bother cleaning up at all and then I’d have had to explain the point above. =)

23 09 2011
Robert Penner (@robpenner)

To me, the example is extremely important. To add API, there needs to be a compelling use case.

In this scenario, the service class should be removing the signal’s listeners every time a transaction completes or times out. Then there isn’t anything left for your feature to do.

23 09 2011
Justin J. Moses

Robert – I’ve updated the example to use Robotlegs. It now pushes shared model changes onto the views for each mediator.

The update signal from the model is exposed to the Mediator however we obviously don’t want to remove all listeners from the shared signal. As such, we use applyTo() to ensure that we can add one or more handlers within an instance and removeAll in one fell swoop without effecting other handlers in other classes/instances.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




Follow

Get every new post delivered to your Inbox.

%d bloggers like this: