Skip to content

Commit

Permalink
Merge pull request #3 from DamienLaurent/master
Browse files Browse the repository at this point in the history
Fix about memory issue that causes multiple mediator instanciations for a unique view.
  • Loading branch information
joelhooks committed Jun 7, 2012
2 parents ff776b4 + fcd75f4 commit 62c70f9
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/org/robotlegs/utilities/modular/base/ModuleMediatorMap.as
@@ -0,0 +1,25 @@
package org.robotlegs.utilities.modular.base
{
import org.robotlegs.base.MediatorMap;
import org.robotlegs.core.IInjector;
import org.robotlegs.core.IReflector;

import flash.display.DisplayObjectContainer;

/**
* @author dlaurent 7 juin 2012
*/
public class ModuleMediatorMap extends MediatorMap
{
public function ModuleMediatorMap( contextView : DisplayObjectContainer, injector : IInjector,
reflector : IReflector )
{
super( contextView, injector, reflector );
}

public function dispose() : void
{
removeListeners();
}
}
}
23 changes: 23 additions & 0 deletions src/org/robotlegs/utilities/modular/base/ModuleViewMap.as
@@ -0,0 +1,23 @@
package org.robotlegs.utilities.modular.base
{
import org.robotlegs.base.ViewMap;
import org.robotlegs.core.IInjector;

import flash.display.DisplayObjectContainer;

/**
* @author dlaurent 7 juin 2012
*/
public class ModuleViewMap extends ViewMap
{
public function ModuleViewMap( contextView : DisplayObjectContainer, injector : IInjector )
{
super( contextView, injector );
}

public function dispose() : void
{
removeListeners();
}
}
}
42 changes: 42 additions & 0 deletions src/org/robotlegs/utilities/modular/mvcs/FixedModuleContext.as
@@ -0,0 +1,42 @@
package org.robotlegs.utilities.modular.mvcs
{
import org.robotlegs.core.IInjector;
import org.robotlegs.core.IMediatorMap;
import org.robotlegs.core.IViewMap;
import org.robotlegs.utilities.modular.base.ModuleMediatorMap;
import org.robotlegs.utilities.modular.base.ModuleViewMap;

import flash.display.DisplayObjectContainer;
import flash.system.ApplicationDomain;

/**
* @author dlaurent 7 juin 2012
*/
public class FixedModuleContext extends ModuleContext
{
public function FixedModuleContext( contextView : DisplayObjectContainer = null, autoStartup : Boolean = true,
parentInjector : IInjector = null, applicationDomain : ApplicationDomain = null )
{
super( contextView, autoStartup, parentInjector, applicationDomain );
}

override public function dispose() : void
{
ModuleMediatorMap( _mediatorMap ).dispose();
ModuleViewMap( _viewMap ).dispose();
super.dispose();
}

override protected function get mediatorMap() : IMediatorMap
{
return _mediatorMap ||
( _mediatorMap =
new ModuleMediatorMap( contextView, injector.createChild( _applicationDomain ), reflector ) );
}

override protected function get viewMap() : IViewMap
{
return _viewMap || ( _viewMap = new ModuleViewMap( contextView, injector.createChild( _applicationDomain ) ) );
}
}
}
31 changes: 31 additions & 0 deletions src_test/LaunchTestSuite.as
@@ -0,0 +1,31 @@
package
{
import testutils.TestStage;

import org.flexunit.internals.TraceListener;
import org.flexunit.runner.FlexUnitCore;
import org.robotlegs.utilities.modular.mvcs.ModuleContextTest;

import flash.display.Sprite;

public class LaunchTestSuite extends Sprite
{
public function LaunchTestSuite()
{
TestStage.stage = stage;

var uniqueTestClass : Class = null;

core = new FlexUnitCore();
core.addListener( new TraceListener() );

if ( uniqueTestClass == null )
core.run( ModuleContextTest );
else
core.run( uniqueTestClass );
}

private var core : FlexUnitCore;
}
}

94 changes: 94 additions & 0 deletions src_test/org/robotlegs/utilities/modular/mvcs/ModuleContextTest.as
@@ -0,0 +1,94 @@
package org.robotlegs.utilities.modular.mvcs
{
import testutils.TestStage;
import testutils.modules.FixedModuleAContext;
import testutils.modules.ModuleAContext;
import testutils.modules.ModuleAMediator;

import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertFalse;
import org.flexunit.asserts.assertTrue;
import org.flexunit.async.Async;

import flash.events.Event;
import flash.events.TimerEvent;
import flash.system.System;
import flash.utils.Timer;

/**
* @author dlaurent 7 juin 2012
*/
public class ModuleContextTest
{

[Before( async )]
public function setup() : void
{
System.gc();
ModuleAMediator.numInstances = 0;

// This strange thing is intended to let the time for the GC to clean memory (sometimes
// it is a bit more longer and listeners from the first test are still there when running the second test).
var delayer : Timer = new Timer( 2000, 1 );
Async.proceedOnEvent( this, delayer, TimerEvent.TIMER_COMPLETE, 3000 );
delayer.start();
}

/**
* With the original ModuleContext, we see that the ADDED_TO_STAGE listeners (viewMap and mediatorMap)
* are not unregistered if the GC didn't pass after the dispose of moduleAContext.
*
* NOTE : This test is a bit random, it can fail if the GC does the garbage collection just after the
* "moduleAContext.dispose()" instruction.
*/
[Test]
public function after_ModuleContext_shutdown__contextView_still_have_ADDED_TO_STAGE_listener() : void
{
// --o Setup
var moduleAContext : ModuleAContext = new ModuleAContext( TestStage.stage, false );
moduleAContext.startup();
// --o Exercise
moduleAContext.dispose();
// --o Verify
assertTrue( TestStage.stage.hasEventListener( Event.ADDED_TO_STAGE ) );
}

/**
* With the fix, the ADDED_TO_STAGE listeners (viewMap and mediatorMap) are released.
*/
[Test]
public function after_FixedModuleContext_shutdown__contextView_shouldnt_have_ADDED_TO_STAGE_listener() : void
{
// --o Setup
var moduleAContext : FixedModuleAContext = new FixedModuleAContext( TestStage.stage, false );
moduleAContext.startup();
// --o Exercise
moduleAContext.dispose();
// --o Verify
assertFalse( TestStage.stage.hasEventListener( Event.ADDED_TO_STAGE ) );
}

/**
* Here, we can see that the memory leak issue has the consequence to instanciate too much mediators
* for a unique view instance.
*
* NOTE : This test is a bit random, it can fail if the GC does the garbage collection just after the
* "moduleAContext.dispose()" instruction.
*/
[Test]
public function with_memory_leak_issue__too_much_mediators_are_created_after_second_instanciation() : void
{
// --o Setup
var moduleAContext : ModuleAContext = new ModuleAContext( TestStage.stage, false );
moduleAContext.startup();
moduleAContext.dispose();
// --o Exercise
assertEquals( 1, ModuleAMediator.numInstances );
// --o Verify
moduleAContext = new ModuleAContext( TestStage.stage, false );
moduleAContext.startup();
// Two mediators have been created for this single module startup.
assertEquals( 3, ModuleAMediator.numInstances );
}
}
}
14 changes: 14 additions & 0 deletions src_test/testutils/TestStage.as
@@ -0,0 +1,14 @@
package testutils
{

import flash.display.Stage;

/**
* Used to access stage while running unit tests.
* @author dlaurent 7 juin 2012
*/
public class TestStage
{
public static var stage : Stage;
}
}
37 changes: 37 additions & 0 deletions src_test/testutils/modules/FixedModuleAContext.as
@@ -0,0 +1,37 @@
package testutils.modules
{
import org.robotlegs.core.IInjector;
import org.robotlegs.utilities.modular.mvcs.FixedModuleContext;

import flash.display.DisplayObjectContainer;
import flash.system.ApplicationDomain;

/**
* @author dlaurent 7 juin 2012
*/
public class FixedModuleAContext extends FixedModuleContext
{
public function FixedModuleAContext( contextView : DisplayObjectContainer = null, autoStartup : Boolean = true,
parentInjector : IInjector = null, applicationDomain : ApplicationDomain = null )
{
super( contextView, autoStartup, parentInjector, applicationDomain );
}

override public function startup() : void
{
injector.mapSingleton( ModuleAView );
mediatorMap.mapView( ModuleAView, ModuleAMediator );

contextView.addChild( injector.getInstance( ModuleAView ) );

super.startup();
}

override public function shutdown() : void
{
contextView.removeChild( injector.getInstance( ModuleAView ) );

super.shutdown();
}
}
}
37 changes: 37 additions & 0 deletions src_test/testutils/modules/ModuleAContext.as
@@ -0,0 +1,37 @@
package testutils.modules
{
import org.robotlegs.core.IInjector;
import org.robotlegs.utilities.modular.mvcs.ModuleContext;

import flash.display.DisplayObjectContainer;
import flash.system.ApplicationDomain;

/**
* @author dlaurent 7 juin 2012
*/
public class ModuleAContext extends ModuleContext
{
public function ModuleAContext( contextView : DisplayObjectContainer = null, autoStartup : Boolean = true,
parentInjector : IInjector = null, applicationDomain : ApplicationDomain = null )
{
super( contextView, autoStartup, parentInjector, applicationDomain );
}

override public function startup() : void
{
injector.mapSingleton( ModuleAView );
mediatorMap.mapView( ModuleAView, ModuleAMediator );

contextView.addChild( injector.getInstance( ModuleAView ) );

super.startup();
}

override public function shutdown() : void
{
contextView.removeChild( injector.getInstance( ModuleAView ) );

super.shutdown();
}
}
}
17 changes: 17 additions & 0 deletions src_test/testutils/modules/ModuleAMediator.as
@@ -0,0 +1,17 @@
package testutils.modules
{
import org.robotlegs.utilities.modular.mvcs.ModuleMediator;

/**
* @author dlaurent 7 juin 2012
*/
public class ModuleAMediator extends ModuleMediator
{
public static var numInstances : int = 0;

public function ModuleAMediator( view : ModuleAView )
{
numInstances++;
}
}
}
14 changes: 14 additions & 0 deletions src_test/testutils/modules/ModuleAView.as
@@ -0,0 +1,14 @@
package testutils.modules
{
import flash.display.Sprite;

/**
* @author dlaurent 7 juin 2012
*/
public class ModuleAView extends Sprite
{
public function ModuleAView()
{
}
}
}

0 comments on commit 62c70f9

Please sign in to comment.