Flex states retaining memory

This is an old post that explains how flex states work, how creating a state creates a Factory that holds a hard reference to the state, and a workaround for states if you want to be able to release the memory of a state.

Since this post, I've learned a few things, and before reading, I want to make a few corrections.
1. There's no need to create the AddDisposableChild IOverride. You can do this more easily by just creating an empty mx:State and listening for the enterState and exitState events. I'll keep the post as it was because I think it's nice to see how to create IOverride classes that can be used in States like AddChild, RemoveChild, etc.

2. I've change my approach a bit to managing memory in Flex. I now like to keep states as Flex intended, never releasing the memory from state to state. This is faster than re-creating the state every time. So instead, I use Modules a lot now. If I have a large section, I separate it off into a Module, which is a good way to have a large section that can be cleared from memory. Of course you will have to consider other reasons why your Module might be stuck in memory; hard listeners to the parent or stage, hard references to the Module or its children from the loader, things like that.

So with that in mind, the rest of the article may or may not be worth reading, sorry if I'm confusing!

___________________________________
Update:
This post will become outdated with the next version of flex:
http://bugs.adobe.com/jira/browse/SDK-17273
In build 6322:
"Adding ITransientDeferredInstance which supports a reset() method, allowing deferred instances to be optionally cleaned up. Compiler support added to generate an associated destruction method which conveniently nulls out any related document slots upon reset.

Added support for a new compile time attribute (itemDestructionPolicy) for states, taking advantage of the new resettable factory."
___________________________________

I use flex states a lot. I think they're a great way to handle navigation within your application. However, I have recently discovered that any mxml component added with AddChild will not and can not be removed from memory. This isn't a problem with small applications, but with large ones, this is pretty awful. It's not technically a memory "leak" because going back and forth between two states won't pile on the memory, but every state you navigate to will never be released.

There are two places where there are strong references to the component you're trying to add. The first is in the AddChild class and the second is in the DeferredInstanceFromFunction class.
The hard reference in the AddChild class can be overcome easily enough by extending the AddChild class.
Before explaining how to make a workaround, here's and explanation how AddChild works
Given the mxml:

<mx:AddChild>
  <Component/>
<mx:AddChild>

When you compile the application, Flex generates an actionscript method that instantiates your component. Then, it takes that method and gives it to DeferredInstanceFromFunction, which acts as a factory for your component. That factory is what AddChild uses. Now in my opinion, there are two mistakes in Flex's code, the first is that when the AddChild.remove is called, the instance isn't de-referenced. The second mistake, which is much worse and more encompassing, is that the factory itself retains a reference to the instance it creates.
Usually the way a factory works is you have a class that contains generator methods for creating instances for you. I've never seen a factory before that will keep the instance for no good reason. It's rather absurd.

So the only solution I could come up with is to avoid using AddChild altogether. The UIComponent.states array takes any instance implementing IOverride. So I just created a rather generic IOverride class that dispatches an event on apply and remove.

package com.nbilyk.states {
	import flash.events.Event;
	import flash.events.EventDispatcher;
 
	import mx.core.UIComponent;
	import mx.states.IOverride;
 
	[Event(name="apply", type="flash.events.Event")]
	[Event(name="remove", type="flash.events.Event")]
	public class AddDisposableChild extends EventDispatcher implements IOverride {
 
		public static const APPLY:String = "apply";
		public static const REMOVE:String = "remove";
 
		public function AddDisposableChild() {
		}
		public function initialize():void {}
		public function apply(parent:UIComponent):void {
			dispatchEvent(new Event(APPLY));
		}
		public function remove(parent:UIComponent):void {
			dispatchEvent(new Event(REMOVE));
		}
	}
}

<mx:states>
	<mx:State name="registrationState">
		<states:AddDisposableChild apply="createRegistrationView()" remove="removeRegistrationView();"/>
	</mx:State>
</mx:states>
<mx:Script>
<![CDATA[
	private function createRegistrationView():void {
		registrationView = new RegistrationView();
		addChild(registrationView);
	}
	private function removeRegistrationView():void {
		removeChild(registrationView);
		registrationView = null;
	}
]]>
</mx:Script>

It makes things a little more verbose than the mxml counterpart, but hey, if you can think of a better way to do states in flex and have the state be released from memory, please let me know!
Unfortunately, as far as I know, there's not a way to overwrite the DeferredInstanceFromFunction class and still use signed RSLs.

Thanks for posting this, it explained the problem very well. I tried this workaround and still the memory was not released. Albeit we are running flex 2 currently, I was hoping it would work. We have also tried the flex ViewStack, but found it has the same problem with holding onto memory after the view has been swapped out, and does not release the memory. I'm hoping this really is fixed in flex 4 - has anybody tried the beta and had a chance to confirm this fix?

thanks for the info on states. A good article on using states for website creation in flex would like to share :
http://askmeflash.com/tutorial_m.php?p=tutorial&tid=0

You can also use the enterState and exitState events to achieve the same result without the extra AddDisposableChild class:

<mx:states>
    <mx:State name="registrationState" enterState="createRegistrationView()" exitState="removeRegistrationView()" />
</mx:states>

Re:

Thanks!

thanks a lot for your investiagtion. it´s really a shame how memory leak issues are handles by the framework!
can you add the link to your bugreport so we can vote for it.

https://bugs.adobe.com/jira/browse/SDK-17273

Here it is, but I doubt Adobe is going to do anything about it, it's such a low-level part of the flex framework that if they tried to fix it, it would likely break a hundred other things in the process.

Nick,
How would address things like relativeTo and postion using your AddDisposable approach? For example
"... "

Re:

Well, it has to be done manually, in your apply handler, you would just addChild where you need it.
If you wanted to do relativeTo fooComponent
Instead of:
addChild(registrationView);
You would do:
fooComponent.addChild(registrationView);

and of course you can do addChildAt to control which position you add to.

Keep in mind that this approach to states is really best only for large applications. If you have a small application, it doesn't really matter if every state is stored in memory. Also, if you are using this approach, make sure you are using the memory profiler to make sure that the states are removing, there may be other things keeping it in memory like binding expressions, listeners not being removed, etc.

I hope you raised this issue on the Flex bug database or at least check the bug database to see if it has already be raised and voted for it to be fixed.

Re:

I submitted a bug for the DeferredInstanceFromFunction and DeferredInstanceFromClass methods. As far as Flex retaining the memory for states using AddChild and never releasing them, I didn't file a bug relating to that because it's working as intended, it's just unfortunate they chose to do it the way they did.
I should also file a feature request for an alternative to AddChild. The current way AddChild is done can be preferable in certain situations, so I'd be happy if they fixed the two IDeferredInstance classes and then added a different AddChild class.