views:

69

answers:

1

So, I've been toying around with Flash, browsing through the documentation, and all that, and noticed that the ENTER_FRAME event seems to defy my expectation of a deterministic universe.
Take the following example:

(new MovieClip()).addEventListener(Event.ENTER_FRAME, 
     function(ev) {trace("Test");});

Notice this anonymous MovieClip is not added to the display hierarchy, and any reference to it is immediately lost.

It will actually print "Test" once a frame until it is garbage collected. How insane is that? The behavior of this is actually determined by when the garbage collector feels like coming around in all its unpredictable insanity! Is there a better way to create intermittent failures? Seriously.

My two theories are that either the DisplayObject class stores weak references to all its instances for the purpose of dispatching ENTER_FRAME events, or, and much wilder, the Flash player actually scans the heap each frame looking for ENTER_FRAME listeners to pull on.

Can any hardened Actionscript developer clue me in on how this works? (And maybe a why - the - f**k they thought this was a good idea?)

+3  A: 

Short answer: you get what you ask for.

The fact that your MovieClip is "anonymous", as you call it, doesn't change how garbage collection works.

Both of your theories are wrong. It's simpler. You asked your MovieClip instance to notify you whenever a frame is entered. Quote from the docs:

Dispatched when the playhead is entering a new frame. If the playhead is not moving, or if there is only one frame, this event is dispatched continuously in conjunction with the frame rate. This event is dispatched simultaneously to all display objects listenting for this event.

And this is what is happening.

Objects such as MovieClips are heap allocated. If you store a reference to it in a local variable, the reference lives in the stack and will get out of scope when you return from your function. You'll have no way to refer to the object, but the object will still be alive, in the heap. So, whether you store a reference to the mc instance or not, that doesn't change this basic fact of how heap and garbage collection works.

Now, since the are no strong refs to this object, this object is elligible for collection. This does not mean it will be collected when the function returns.

Since the object is still alive, it will continue to dispatch the event, because you asked for it. Once it's collected, naturally, your code in the handler will no longer be executed.

Edit

I said that both of your theories are wrong. I think that's the case from an Actionscript perspective. However, at the player level, it seems evident that the player has to keep track of objects on which it must fire certain events, so yes, it must be storing an internal reference to said objects. This is no different to how it works for other objects that are globally handled by the player, though, such as for instance, loaders. There's one exception to this that I'm aware of: running Timers. As long as a Timer is running, it won't be collected, even though you don't have any Actionscript reference to it.

Juan Pablo Califano
Deleted my response, don't want to cause confusion. And you are right. That makes much more sense. I always assumed to be that way due to as3 'experts' legends about listeners and their references..
MrKishi
@MrKishi. Well, I've seen legends getting this wrong, even in SO (I don't mean lengends sarcasticly; these are guys that have done amazing things). Anyway, one of the most common references for memory and GC are probably Grant Skinner's blogs. But if you read them, he doesn't say adding listeners will cause a leak. He strongly advocates defaulting to weak refs and although I might not agree, his points are valid. The problem is many people just get that part and move on. So they have the simplified (and wrong) notion that not removing a listener or not using a weak ref will always cause leaks.
Juan Pablo Califano
@Juan: I don't know why the fuss about weakrefs, as well. I never use (anonymous?) functions, anyway. Wouldn't make a difference.
MrKishi
@MrKishi. So, bottom line is: there's nothing inherintly different with event listeners. It works just like everything else, GC-wise. If you understand how GC works, apply that to listeners and you'll be fine. It's equivalent to `dispatcher.addReference(someObject)`, where `dispatcher` stores a ref to `someObject`, but not the other way round. If `someObject` is collected, `dispatcher` will be collected too (as long as dispatcher is not global -think stage-, a special case -Timer- or shared with other objects). You usually want to call removeEventListener, but for other reasons, not GC.
Juan Pablo Califano
@MrKishi. Weakly referenced listeners are a pet peeve of mine, I guess. It's not so much the mechanism that I'm against. Rather, I don't like the reason behind their use in most cases. People advocating weakrefs will tell they're better because if you forget to remove listeners, you'll not cause a leak. In a way, they encourage sloppiness, so I think they do little favor to anyone trying to get their code right.
Juan Pablo Califano
The thing is.. I would _never_ use a weak reference on a method as there's no need to. And if I had to use regular functions, I would actually need them. The whole *I always use it 'cause it makes life easier* would actually be a hassle as I'd just type more and forget about it when really needed.
MrKishi
@MrKishi. We think the same way about this, then! Use them if needed, but don't use them by default 'just in case'. 'Just in case' in this context usually means a bug (or sloppiness) in your code (if you don't want to listen to an event anymore, call removeEventListener). By masking this problem, you do yourself a disservice IMO (and probably add more indeterminacy to the whole thing). Understanding how the thing works and why it works the way it works is much better.
Juan Pablo Califano
I'm accepting this as the correct answer, in truth, I asked the question more to vent than anything! What really bugs me about this phenomenon, is that addEventListener should just add a function to an EventDispatcher's (in this case a MovieClip's) internal list of listeners. So, in my code snippet, there are no external references to the object, and damn-right it's GC eligible, 'cause it's unfindable! It's just that, for this particular event, the Flash player reaches into the virtual machine in an odd way.
nstory