Saturday, March 20, 2010

Unbind mxml (curly brace) bindings in Flex

Flex developers love the inline curly brace bindings. We just cannot do without it but along with that comes the pain of performance and memory leaks. I will not talk about performance in this blog but I will show you how you can remove these bindings.

The first question is, why should I remove the bindings? Why doesn't this happen automatically? Lets understand this simple component and its references...

//MyComponent.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300">
<mx:Script>
<![CDATA[
[Bindable] private var _firstName:String;
]]>
</mx:Script>
<mx:TextInput id="myTextInput" text="{_firstName}"/>
</mx:HBox>

What happens if another object someObj is listening to the myTextInput keyDown (or any other) event. Lets understand the references...
- MyComponent has a reference on myTextInput because it is a child of MyComponent.
- myTextInput has a reference on MyComponent because of the parent property and text binding.
- someObj has a reference on myTextInput because it is listening to the keyDown event (assuming it was not a weak event listener).

When you expect the MyComponent to get garbage collected, it won't because someObj references myTextInput and myTextInput references MyComponent. MyComponent will be garbage collected only if myTextInput's references to MyComponent are released. To make this happen, we will have to call MyComponent.removeAllChild() and remove all bindings. removeAllChild() is straight forward, lets look at bindings.

The right way to bind is through ActionScript. Keep a handle to the ChangeWatcher returned by BindingUtils.bindSetter() and BindingUtils.bindProperty(). When needed, just call changeWatcher.unwatch() and remove the binding. But there is no concept of changeWatchers in mxml bindings. So how do we release them?

Click here for a demo
View source: Right click demo, or download from here.

As you can see, once the binding is removed, the textInputs stop responding to the change. Before you scroll down to the complete code, lets understand what are these bindings, how do they look like and how to remove them.

Here is what an as3-generate binding looks like...

var result:Array = [];
var binding:Binding;

binding = new mx.binding.Binding(this,
function():String
{
var result:* = (int1);
var stringResult:String = (result == undefined ? null : String(result));
return stringResult;
},
function(_sourceFunctionReturnValue:String):void
{
_testRemoveCurlyBraceBindings_Label2.text = _sourceFunctionReturnValue;
},
"_testRemoveCurlyBraceBindings_Label2.text"
);
result[0] = binding;

...
...
...
mx_internal::_bindings = mx_internal::_bindings.concat(bindings);
mx_internal::_watchers = mx_internal::_watchers.concat(watchers);

The Flex compiler generates this type of binding code in the as3-generated file and keeps adding it to the result array.

Every generated actionscript (from mxml component) also contains mx_internal::_bindings, mx_internal::_bindingsByDestination, mx_internal::_bindingsBeginWithWord and mx_internal::_watchers properties. I know this is enough information for you to open Flex/Flash Builder and start playing with the mx_internals. But you can save some time by reading a few lines more. These mx_internal variables contain the mxml/curly brace binding details and all you need to do is get rid of them. Here is the function I wrote that releases the mxml bindings.


private function disposeAllCurlyBraceBindings():void {
var i:int = 0;
var len:int = 0;

//tear bindings
var myBindings:Array;
var bindDestinations:Object;
var bindBeginWithWord:Object;
try {
myBindings = mx_internal::_bindings;
bindDestinations = mx_internal::_bindingsByDestination;
bindBeginWithWord = mx_internal::_bindingsBeginWithWord;
mx_internal::_bindings = null;
mx_internal::_bindingsByDestination = null;
mx_internal::_bindingsBeginWithWord = null;
} catch (err:Error) {
trace("TEARDOWNVIEW_ERROR processing bindings");
} finally {
if (myBindings) {
len = myBindings.length;
for (i = 0; i < len; i++) {
var binding:Object = myBindings.pop();

if (binding) {
binding.mx_internal::isExecuting = false;
binding.mx_internal::isEnabled = false;
binding.mx_internal::isHandlingEvent = false;
binding.mx_internal::destFunc = null;
binding.mx_internal::srcFunc = null;
binding.mx_internal::destString = null;
binding.mx_internal::disabledRequests = null;
binding.twoWayCounterpart = null;
binding.uiComponentWatcher = -1;
binding.mx_internal::document = null;
}
}
}
deleteAllDynamicProperties(bindDestinations);
deleteAllDynamicProperties(bindBeginWithWord);
}

//tear watchers
var myWatchers:Array;
try {
myWatchers = mx_internal::_watchers;
mx_internal::_watchers = null;
} catch (err:Error) {
trace("TEARDOWNVIEW_ERROR processing watchers");
} finally {
if (myWatchers) {
len = myWatchers.length;
for (i = 0; i < len; i++) {
var watcher:Object = myWatchers.pop();

if (watcher) {
if (watcher.hasOwnProperty("value")) {
watcher["value"] = null;
}
}
}
}
}
}

private function deleteAllDynamicProperties(obj:Object):void {
if (!obj) {
return;
}

for (var str:String in obj) {
delete obj[str];
}
}



Explanation:
Get a handle to the mx_internal binding properties. Do this in a try/catch block because some ui components like Repeater and ActionScript components do not contain these properties. If they exist than set their internal properties to null. Also set the mx_internal bindings to null.


I am working on a project where almost all the custom components leak. Every component I profiled had 100-350 references. It was alomost impossible to analyze so many references, cross references and circular references. I have written a generic method that tears down the view (as much as possible) and removes most of the references. After calling this method, my reference count came down to 10-20. This helped me immensely in the reference analysis and fixing the memory leaks.

Also read my blog on removing mxml/inline event listeners and how to get rid of them.

Labels: , ,

9 Comments:

At September 9, 2010 at 8:17 PM , Blogger sunild said...

Hey Prashant, you are so right, binding is the most abused thing I see in (other people's) Flex apps!

I've written a ton of Flex code without using [Bindable], I think developer's just get caught up in the convenience of the whole thing. I'm not saying don't mark something as Bindable, but think twice before you do :)

Sunil

 
At March 16, 2011 at 10:47 PM , Blogger Sam said...

hi Prashant,
This is a very useful info. However, can you suggest where should we call this dispose binding method in a component life cycle to be more effective?
I guess 'remove' event is the best candidate. what would you suggest?

-Samir

 
At April 6, 2011 at 10:13 AM , Blogger Prashant Jain said...

Sam,
Sorry for the late response. I dont get emails from blogger and I dont know why.

During the lifecycle of the component, you would know when it is removed from the displayList and the component is no longer needed. That is when you will dispose the bindings and events. At VMWare, I have written a DisposeManager which does a lot more than remove bindings. This class + a GC invoker class helped us release a Flex application which was crashing every 5 mins.

 
At August 9, 2012 at 12:13 AM , Blogger इंद्रजित पिंगळे said...

Hi Prashant,

First of all, thanks for blog and code. Its very helpful.
But when I use your code to unbind all MXML binding. It creates another instance of my Panel Class. Could you guide me why?

Thanks,
Indrajit

 
At August 16, 2012 at 12:47 AM , Anonymous John said...

Hi Prashant,

Any chance of getting a copy of your DisposeManager code :)

Regards
john

 
At August 16, 2012 at 4:26 AM , Blogger Prashant Jain said...

Indrajit,
I have never encountered such a thing. It is very easy to debug your problem. Put a break point in the unbinding code. When that break point is hit, put a break point in the constructor of the Panel. When the Panel break point is hit, look at the stack trace to see what code invoked the creation of the Panel.

 
At August 16, 2012 at 4:32 AM , Blogger Prashant Jain said...

This comment has been removed by the author.

 
At August 16, 2012 at 4:38 AM , Blogger Prashant Jain said...

John,
I would love to share the DisposeManager and the GC code. Unfortunately, it is the property of VMWare and I will need permission from the legal team to share it. I will talk to them and see where it goes.

The DisposeManager (basically eliminate as many references as possible) was very tricky. Too much null'ing of references can lead to null pointer errors. Too less null'ing can create memory leaks. I remember spending 3 months on these few hundred lines of code. There was a lot of testing involved to make sure there was enough reference elimination.

 
At August 16, 2012 at 5:00 PM , Anonymous John said...

Prashant,

Thanks for the response, would be great if you could share that code.

Thanks again
john

 

Post a Comment

Subscribe to Post Comments [Atom]

<< Home