[Estimated Reading Time: 4 minutes]

So far we’ve seen a multicast event implementation in (fairly limited) action, and dissected the core of it’s implementation, which was a fairly dry affair.

I also demonstrated a flaw in the initial implementation – a susceptibility to objects adding handlers to events but not removing them when being destroyed.  Before the .NET crowd get all smug, we should note that the relationship between an event source and it’s listeners is potentially problematic, in .NET also.

Fortunately I devised a solution to the problem in my framework.  The solution – rather neatly – was itself provided by a multicast event.

The video demonstrated the problem, but it might be worth simplifying the problem and looking at it in terms of an Source object with some Event, and a Listener with some Handler.  We can create the problem that we need to solve very easily:

  Source.Event.Add( Listener.Handler );
  Listener.Free;
  Source.Event.DoEvent;

Our problem is that when Listener is free’d the Source object is most likely oblivious to that fact, and the Event certainly is.  We could simply require that every object that adds handlers to events be careful to remove them:

  Source.Event.Add( Listener.Handler );
  Source.Event.Remove( Listener.Handler);
  Listener.Free;
  Source.Event.DoEvent;

An obvious and straightforward solution, but it will soon get tedious and if even one handler is missed and left “dangling” in some event somewhere the problem remains to all intents and purposes unsolved

In a lot of observer/observable implementations I have worked with (which is essentially the pattern we are dealing with after all) some form of “event manager” is used to co-ordinate the relationships between event sources and the listeners.  This simplifies things to an extent – when implementing our listener we now need only remember to notify the event manager when we are destroyed, and it will clean up our event handlers for us after walking through all the relationships and finding those ones in which our listener is involved.

But the event manager itself almost certainly places it’s own demands on an implementation, and in a system with a lot of event sources and/or listeners however, the implementation of the event manager itself can become quite complex and a potential drag on performance.

My solution is to provide a framework for easily establishing the necessary communication between event sources and listeners, and limiting that communication to only the sources and listeners directly involved in a relationship.

The solution is not completely automatic, but the little work that is required in many cases is actually work that you would have found desirable to do – if not essential anyway.

Communication Is A Two Way Process

Any object that has handlers for multicast events is, by definition, participating in a multicast enabled framework.  The most basic need in such a framework is for “object has been destroyed” type notifications, so it makes sense to have a multicast equivalent to an OnDestroy event.

That is, in our condensed example our Listener is highly likely to implement an On_Destroy event of it’s own, which it will call in it’s destructor chain.

Remember when we add a handler to a multicast event we are actually working with two pointers – a pointer to the handler method (Code) and a pointer to the instance of the class (Data) that provides that method.  In other words the Data pointer is the Listener object.

If the Listener object supports an On_Destroy event then the event it is listening to can add it’s own handler to the listener’s On_Destroy event and respond by removing any handlers implemented by that listener.

But given an untyped pointer, how can we determine whether a given listener supports an On_Destroy event, and for that matter, get a reference to that event so that we can add a handler?

Staring Me Right In The Interface

The answer is of course an interface.  An event handler must be a procedure of object, so whatever that Data pointer points to it must be a TObject and given one of those, querying and obtaining interfaces is straightforward.

So I define a standard interface IOn_Destroy:

  IOn_Destroy = interface
  ['{A3670CB7-683A-4C61-8B51-6531FA9559CA}']
    function get_On_Destroy: TMultiCastNotify;
    property On_Destroy: TMultiCastNotify read get_On_Destroy;
  end;

Some changes were then made in TMultiCastEvent – notice that these changes are in the base class, so all multicast event classes benefit.

First, the handler that will respond when a listener is destroyed – this is of course a TNotifyEvent handler since the On_Destroy event is a TMultiCastNotify:

  procedure TMultiCastEvent.ListenerDestroyed(aSender: TObject);
  var
    i: Integer;
    method: PMethod;
  begin
    for i := 0 to Pred(Count) do
    begin
      method := fMethods[i];
      if (method.Data = Pointer(aSender)) then
      begin
        Dispose(method);
        fMethods[i] := NIL;
      end;
    end;

    fMethods.Pack;
  end;

This finds the handler methods implemented by the instance that is being destroyed and removes them.  The second change is to identify listeners to the event that support IOn_Destroy and add this listener to those events, so we add the following to the Add() method of TMultiCastEvent:

    obj := TObject(aMethod.Data);
    if Supports(obj, IOn_Destroy, listener) then
      listener.On_Destroy.Add(ListenerDestroyed);

But wait.  Surely all this does is reverse the problem?  If the Source object is destroyed before the Listener it will destroy the Event and when the Listener fires it’s On_Destroy the Event will no longer be there!

This is a problem, but we can solve that easily enough with a small change to the loop that disposes the method records in the destructor of the event itself:

    for i := 0 to Pred(fMethods.Count) do
    begin
      obj := TObject(PMethod(fMethods[i]).Data);

      if Supports(obj, IOn_Destroy, listener) then
        listener.On_Destroy.Remove(ListenerDestroyed);

      Dispose(fMethods[i]);
    end;

There remains one small gap in all this housekeeping but it is – so far as I can tell – harmless. No points or prizes, but can you see what it is?

Recap

I have extended the initial, basic implementation to take care of some useful housekeeping for us – something that might even make your C#/.NET friends a little jealous!

🙂

As last time, the test project containing these housekeeping changes is available for download for you to play with.

I’m not quite done yet.  I have one more post to offer in which I will cover a number of further minor additions and refinements to the implementation that I have found to be useful.  The fully documented and polished implementation – complete with those refinements – will accompany that post.