9

Usually event listeners shouldn't outlive object that registered them.

Does it mean that event listeners should be held by weak references by default (stored in weak collections by the object listeners are registered on)?

Are there valid cases when listener should outlive its creator?

Or maybe situation like that is a mistake and it shouldn't be allowed?

  • Weak references are usually represented by instances, and these instances can also accumulate to the point where they must be collected as garbage. So it is not a free lunch. The same logic that clears out the weak references could clear out strong references. – Frank Hileman Apr 9 '17 at 0:56
7
+50

Why shouldn't event listeners outlive the object that registered them? It seems like you are assuming event listeners should registered by methods of controls(if we take the GUI example) - or more precisely, methods by objects of classes that inherit the GUI toolkit's controls. That's not a necessity - you could, for example, use a specialized object for registering event listeners and ditch that object afterwards.

Also, if event listeners were weakly referred, you would have to actually keep references to them even if you never use that reference. Failing to do so will make the listener be collected at a random time. So, we get a bug that is

  • Easy to create by mistake(all you have to do is to forget storing an object in a reference variable that you'll never use).
  • Hard to notice(you'll only get that bug if the GC collect that object).
  • Hard to debug(in the debug session - which always works like a release session - you'll only encounter that bug if the GC collected the object).

And if avoiding that bug is not good enough incentive, here are some more:

  1. You'll have to think of a name for each listener you create.

  2. Some languages use static anlysis that'll generate a warning if you have a private member field that's never get written or never get read. You'll have to use have a mechanism for overriding that.

  3. The event listener does something, and once the object that has it's strong reference is collected it'll stop doing that something. You now have something that affects the program's state and depends on the GC - which means the GC affects the concrete state of the program. And this is BAD!

  4. Handling weak references is slower, since you have another level of indirection and since you need to check if the reference was collected. This wouldn't be a problem if having event listeners in weak references was necessary - but it isn't!

5

In general, yes, weak references should be used. But first we have to be clear about what you mean by “event listeners”.

Callbacks

In some programming styles, especially in the context of asynchronous operations, it is common to represent a part of a calculation as a callback that gets executed on a certain event. For example a Promise [1] may have a then method that registers a callback upon completion of the previous step:

promise =
    Promise.new(async_task)                # - kick off a task
    .then(value => operation_on(value))    # - queue other operations
    .then(value => other_operation(value)) #   that get executed on completion
... # do other stuff in the meanwhile
# later:
result = promise.value # block for the result

Here, the callbacks registered by then have to be held by strong references, as the promise (the event source) is the only object holding a reference to the callback. This is not an issue as the promise itself has a limited lifetime, and will be garbage collected after the chain of promises is completed.

Observer Pattern

In the observer pattern, a subject has a list of dependent observers. When the subject enters some state, the observers are notified according to some interface. Observers can be added to and removed from the subject. These observers do not exist in a semantic vacuum, but are waiting for events for some purpose.

If this purpose does no longer exist, the observers should be removed from the subject. Even in garbage-collected languages, this removal might have to be performed manually. If we fail to remove an observer, it will be kept alive via the reference from the subject to the observer, and with it all objects that the observer references. This wastes memory and degrades performance as the (now useless) observer will still be notified.

Weak references fix this memory leak, as they allow the observer to be garbage collected. When the subject goes around to notify all observers and finds that one of the weak references to an observer is empty, that reference can be safely removed. Alternatively the weak references might be implemented in a way that allows the subject to register a cleanup callback that will remove the observer upon collection.

But note that weak references are only a band-aid that limit the damage by forgetting to remove an observer. The correct solution would be to make sure that an observer is removed when no longer needed. Options include:

  • Doing it manually, but that's prone to errors.

  • Using something akin to try-with-resource in Java or using in C#.

  • Deterministic destruction, such as via the RAII idiom. Note that in a language with deterministic garbage collection, this might still require weak references from the subject to the observer in order to trigger the destructor.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.