DataBinding under the hood (part 3): Generated Code

Published by Manfred Karrer on Sunday, 26 of April , 2009 at 00:28

So now lets get our hands dirty and check out the generated code which is created behind the scenes.

When you compile a Flex application the compiler runs the compilation in 2 steps:

  • The mxmlc compiler generates a lot of Actionscript classes
  • The compc compiler compiles from the generated classes plus your custom classes the swf file

These 2 steps are normally not visible, because the generated code is not stored in your project. If you add the compiler argument -keep (or -keep-generated-actionscript=true) in your compiler settings in Flex Builder, you will see a generated folder inside the src folder containing a lot of Actionscript files.

There are different kinds of files generated:

  • Classes defining the default style for Flex components
  • Classes holding the Resourcebundle properties
  • Classes for embedded skins
  • Classes for Flex Application setup
  • Classes for Flex DataBinding

The style, skin and property files are out of our scope here and has no direct relation to this topic.
The classes which are used for setting up a Flex Application are our entry point, but will not be discussed in detail (even it would be very interesting to have a closer look to Mixins, [Frame] metadata tags and all these exotic stuff…).

So lets look at our relevant classes for DataBinding (all others has been removed):

generated

The entry-point of our Flex application is _DBGC_mx_managers_SystemManager.
This class extends SystemManager. The SystemManager is the main class and the first DisplayObject added to the stage. It does a lot of setup stuff.
At the first frame in our MovieClip the preloading is handled. At the second frame the Application (DBGC) is instantiated. In the static info() method the mixins classes are defined. At frame 2 the mixin classes are inited via the static method call init().

The DBCG class (file: DBCG-generated.as) is our Application class.
Here the MXML components are converted to UIComponentDescriptors which handles the instantiation of these Components.
At initialize() an array of Binding objects are created.
The Binding class use anonymous functions for resolving the source and destination properties.

[code lang="actionscript3"]
var binding:Binding = new mx.binding.Binding(this,
    function():String
    {
        var result:* = (bindableVO.myBindableProp);
        var stringResult:String = (result == undefined ? null : String(result));
        return stringResult;
    },
    function(_sourceFunctionReturnValue:String):void
    {

        myLabel.text = _sourceFunctionReturnValue;
    },
    "myLabel.text");
[/code]

The _DBGCWatcherSetupUtil creates an array of PropertyWatcher objects, setup the relationship for chains and pass the Binding object to the PropertyWatcher.

[code lang="actionscript3"]
watchers[0] = new mx.binding.PropertyWatcher("bindableVO",
    { propertyChange: true },[ bindings[0] ], propertyGetter);
watchers[1] = new mx.binding.PropertyWatcher("myBindableProp",
    { propertyChange: true },[ bindings[0] ], null);
watchers[0].updateParent(target);
watchers[0].addChild(watchers[1]);
[/code]

After the basic setup execute() is called on every Binding object.
Here is the complete code:

[code lang="actionscript3"]
override public function initialize():void
{
 	mx_internal::setDocumentDescriptor(_documentDescriptor_);

	var bindings:Array = _DBGC_bindingsSetup();
	var watchers:Array = [];

	var target:DBGC = this;

	if (_watcherSetupUtil == null)
	{
		var watcherSetupUtilClass:Object = getDefinitionByName("_DBGCWatcherSetupUtil");
		watcherSetupUtilClass["init"](null);
	}

	_watcherSetupUtil.setup(this,
				function(propertyName:String):* {
                                     return target[propertyName];
                                },
				bindings,
				watchers);

	for (var i:uint = 0; i < bindings.length; i++)
	{
		Binding(bindings[i]).execute();
	}

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

	super.initialize();
}

private function _DBGC_bindingsSetup():Array
{
    var result:Array = [];
    var binding:Binding;

    binding = new mx.binding.Binding(this,
        function():String
        {
            var result:* = (bindableVO.myBindableProp);
            var stringResult:String = (result == undefined ? null : String(result));
            return stringResult;
        },
        function(_sourceFunctionReturnValue:String):void
        {

            myLabel.text = _sourceFunctionReturnValue;
        },
        "myLabel.text");
    result[0] = binding;

    return result;
}
[/code]

So lets jump into the Binding Class. This is a regular Framework class.
The execute() method is basically applying the source value to the destination. In-between there is a lot of error handling going on, but basically it´s nothing else as reading out a value and setting it to the destination. No event dispatching is included until now.

The setting of the value happens on start-up. If the bindable property changes later, event dispatching comes in.
This happens in the PropertyWatcher class:
An event handler is added at the source object and the PropertyWatcher is listening for the defined event type. It is invoked with weak reference, so the fact that MXML Bindings cannot be removed will not lead to memory leaks.
The event handler checks if the dispatched PropertyChangeEvent contains the same property name as our PropertyWatcher represents and calls notifyListeners() if the property name matches.
On every Binding object which is registered as listener watcherFired() is called.
And here again the execute() method is responsible for passing the value from the source over to the destination.

The last missing link is the class which dispatch the PropertyChangeEvent.
The BindableProperty (file: _BindablePropertyVO-binding-generated.as) is the place where this happens.

[code lang="actionscript3"]
[Bindable(event="propertyChange")]
public function get myBindableProp():String
{
    return this._834029286myBindableProp;
}

public function set myBindableProp(value:String):void
{
    var oldValue:Object = this._834029286myBindableProp;
    if (oldValue !== value)
    {
        this._834029286myBindableProp = value;
        this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this,
                                  "myBindableProp", oldValue, value));
    }
}
[/code]

Here the gettter/setters are created for our [Bindable] property. The compiler defines “propertyChange” as event type – [Bindable(event=”propertyChange”)] and dispatches a PropertyChangeEvent if the value has changed.
If the class containing the bindable property is not a subclass of EventDispatcher the compiler set the classes interface of BindableProperty to IEventDispatcher, adds the methods of IEventDispatcher and uses an instance of EventDispatcher via composition.

Note that this code is only generated when you don´t use custom events in the [Bindable] metadata tag ([Bindable(event=”myCustonEvent”)]).

I don´t know how this class is linked into the Application, it has no reference anywhere. I guess the compiler do this job behind the scenes, as well as with the interface classes and some other stuff.

Lets have a look to the moments when DataBinding is executed:

At Application start-up the Binding is executed for the first time.
Here is the call stack (Breakpoint at Binding.execute():

dbseq1

Due the fact that the component is not created yet, the Binding fails in a catch Block at wrapFunctionCall() in the Binding class. The catch block is pretty slow, i am wondering why this initial execute() is not prevented? At least the try/catch could be replaced with a check against null which is much faster then falling into the catch block. I guess in large applications this could noticeable slow down start-up time.

At the time the component is added to the Application executeBinding() is called and triggers the second execute().

dbseq2

At this point the component is valid but there is only the value passed which is defined in the property declaration. This value is often not set at this momens so it is null.

If you setup your initial value at the initialize event a third run will be triggered:

dbseq3

You can prevent this if you set the value right at the variable declaration (if possible) and so prevent to trigger DataBindings third round.

Conclusion
Finally we are through the basic parts how DataBinding is working behind the scenes. To go further it´s best to step through the generated code and try out different scenarios.
We only have had a look into Binding in MXML. If you use BindingUtils just the code for event dispatching from the [Bindable] metadata tag is created.

The generated code for DataBinding with MXML do a lot of error handling. it´s clear that it has some performance drawbacks but gives the benefit that it is very fault-tolerant. You also should consider that there are a lot of different features and use cases for Binding (functions, expressions, 2-way Bindings,…) which has to be handled.

For large applications BindingUtils are the better choice and prevents several performance drawbacks, specially at start-up time.

Comments (3)

Category: Actionscript,Flash,Flex

3 Comments

Comment by Nikos

Made Friday, 17 of December , 2010 at 18:19

thx 4 the article,

how much has this changed in flex 4?

Comment by Manfred Karrer

Made Friday, 17 of December , 2010 at 19:59

I have not looked into Flex 4 code, but i guess that has not changed essentially.

Comment by Nikos

Made Saturday, 18 of December , 2010 at 10:41

cool thx

Sorry, the comment form is closed at this time.