Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What keeps locally scoped objects alive when listeners are registered?

I see this type of apparent magic in all sorts of AS3 code, but here is a reduced example:

package {
    import flash.display.Sprite;
    import flash.events.*;
    import flash.net.*;

    public class URLLoaderExample extends Sprite {
        public function URLLoaderExample() {
            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, onComplete);
            loader.load(new URLRequest("example.txt");
        } // 'loader' should fall out of scope here!

        private function onComplete(evt:Event):void {
            var loader:URLLoader = URLLoader(evt.target);
            trace ("Received data: " + loader.data);
            //unsure if removal below is necessary (since I don't
            //know where 'loader' itself is hiding!)...
                //  - NOTE: this removal is never in the examples!
            loader.removeEventListener(Event.COMPLETE, onComplete);
        }
    }
}

As indicated in the code comment, the loader variable should fall out of scope after the URLLoaderExample constructor. However... it still seems to be kept alive (not garbage collected) somewhere since the onComplete listener/handler is able to receive it cleanly.

Where is the magic/hidden/global reference to loader that keeps it alive so that it can complete it's load operation, and then be handed to the onComplete listener/callback? Can this reference be seen somewhere?

To help with context... as a similar example, I know that the loader instance will have the onComplete listener registered. I also know I need to be careful to use removeEventListener at all times (?) to avoid potential memory leaks resulting from stranded listeners. What concerns me is that I don't understand where the magic loader reference is and whether (or when) I need to clean that up.

Is it maybe the loader.load() call itself that stuffs loader somewhere globally?

like image 617
Russ Avatar asked Jul 05 '11 06:07

Russ


3 Answers

This example is definitely error-prone, since loader may get garbage-collected before loading is finished. When you subscribe to COMPLETE event with onComplete method, you create a reference from loader to your class URLLoaderExample. And what you need to make sure GC will no ruin the loader is creating a reference to it.

GC never guarantees you timely cleaning though, even when you explicitly kill all the references. (See this post for resources on GC logics.) But it can garbage-collect a loader in the process if there are no explicit references to it. If you try your test in an app that uses memory (and is not just sitting there doing nothing), you are likely to see this behaviour. And you are much more likely to see loader garbage-collected if you try loading swfs instead of data.

Using weak references does not help here, because when you do so, you tell GC: "feel free to kill what I, the dispatcher, am referencing, I have no pity for it." In your example it would be like: "feel free to kill URLLoaderExample instance if it looses other viable references", which is, well, pointless. Here's one good article on useWeakReference.

Listeners do not prevent a dispatcher from being garbage-collected. An inactive object is one that no longer has any references to it from other active objects. So, if an object itself has references to something external, it does not prevent this object to be removed from the memory.

So, to answer your question briefly: the reference is nowhere, you are just lucky to see loading working correctly. Well, to be completely precise, it's the function activation object (as it is called in ECMA spec), that is used as a scope for local variables and references them. But anyway, it gets disposed on method return, and you can never get a reference to activation object itself (again by spec).

EDIT Some more words on who holds who from being garbage-collected. Added due to apparent misunderstanding in comments.

A quotation from Adobe livedocs:

useWeakReference:Boolean (default = false) — Determines whether the reference to the listener is strong or weak. A strong reference (the default) prevents your listener from being garbage-collected. A weak reference does not.

So, subscription to event creates a reference FROM dispatcher TO listener. And dispatcher is free to go, unlike listener. Listeners do not prevent a dispatcher from being garbage-collected. And dispatcher CAN prevent listeners from being garbage-collected, which is why we have useWeakReference.

like image 68
Michael Antipin Avatar answered Oct 19 '22 05:10

Michael Antipin


When you add an event listener, you implicitly create a reference to the loader object (by default). However, you can remove that by setting the eventlistener to a "weak" reference.

Here's how you'd do that:

loader.addEventListener(Event.COMPLETE, onComplete, false, 0, true);

The last argument sets "useWeakListener" to true, which means a reference to the loader will not be made. In that case, the loader should be GC'ed.

The important thing to keep in mind is that if you add an event listener with a strong reference, you need to remove it (as you did in the example). If you use a weak listener, you'll need to make the loader a private variable in the class otherwise your callback is in a race situation with the GC.

Here's the documentation on the method: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/IEventDispatcher.html

like image 20
Shakakai Avatar answered Oct 19 '22 05:10

Shakakai


Well man, if your question was why the loader is alive out of scope, here is your answer:

In fact, the loader you see in the Class Constructor Method (CCM) is not the same you see in onComplete: LINE14: var loader:URLLoader = URLLoader(evt.target);

(I dont know why does people like to put same name to variables, that can be confusing, but it is not in the case now that i will explain)

The magic lies in the evt.target. But you should be asking yourself: "What does the .target do?" Well it is a instance variable defined by the "Event Object", and it provides reference to the "Target Object".

If you dont know what a "Event Target" is, read this paragraph. The "Event Target" is the object in which the listener registered at LINE9: loader.addEventListener(Event.COMPLETE, onComplete); As you can see, it is the loader at the CCM (please dont confuse the loader names) which refers to new URLLoader();. Paragraph Conclusion: the "Event Target" is the URLLoader object referenced by the load local variable at the CCM.

Well, using the .target variable you can have reference to the URLLoader object and then use the reference as you want. In the case, you used the reference to remove the listener. It is okay.

Here is a improved version (just one instance variable that provides reference hapily to URLLoader):

package {
    import flash.display.Sprite;
    import flash.events.*;
    import flash.net.*;

    public class URLLoaderExample extends Sprite {
        private var lalala:URLLoader;
        public function URLLoaderExample() {
            lalala = new URLLoader();
            lalala.addEventListener(Event.COMPLETE, onComplete);
            lalala.load(new URLRequest("example.txt");
        }

        private function onComplete(evt:Event):void {
            trace ("Received data: " + lalala.data);
            lalala.removeEventListener(Event.COMPLETE, onComplete);
        }
    }
}

But just to make shure you arent confused with the names:

package {
    import flash.display.Sprite;
    import flash.events.*;
    import flash.net.*;

    public class URLLoaderExample extends Sprite {
        public function URLLoaderExample() {
            var blabla:URLLoader = new URLLoader();
            blabla.addEventListener(Event.COMPLETE, onComplete);
            blabla.load(new URLRequest("example.txt");
        } // 'loader' fell out of scope here! and it fell look there

        private function onComplete(evt:Event):void {
            var phopho:URLLoader = URLLoader(evt.target);
            trace ("Received data: " + phopho.data);
            loader.removeEventListener(Event.COMPLETE, onComplete);
        }
    }
}

Cheers... If you have any doubts at the casting operation used at URLLoader(evt.target); you can ask.

like image 1
Gabriel Avatar answered Oct 19 '22 03:10

Gabriel