Sunday, June 3, 2012

Weak interface references

We all know how painful it can be working with interfaces and reference counting when it comes to circular or cross references.

Vincent Parrett wrote about that a while ago and presented a nice solution.

The only disadvantage about his solution was the special class type (TWeakReferencedObject) you have to inherit from to use a weak reference to. What if you want to use a weak reference to something that already exists and that you cannot change?

That is where my idea comes in.

The Weak<T> type supports assignment from and to T and makes it usable as if you had a variable of T. It has the IsAlive property to check if the reference is still valid and not a dangling pointer. The Target property can be used if you want access to members of the reference.

Let's assume you have that typical parent child relationship where both have a reference to each other. Normally that would cause a memory leak because that cross references would keep both objects alive. Change the parent reference to be a weak reference and both objects get destroyed properly because the child is not keeping the parent alive.

  TParent = class(TInterfacedObject, IParent)
    FChild: IChild;
    procedure AddChild(const AChild: IChild);
    destructor Destroy; override;

  TChild = class(TInterfacedObject, IChild)
    FParent: Weak<IParent>;
    constructor Create(AParent: IParent);

    function GetParent: IParent;

So how to check if the object of the reference is still valid? That is done by hooking the TObject.FreeInstance method so every object destruction is noticed and if a weak reference for that object exists it gets removed from the internal list where all weak references are stored.

While it works I am aware that this is hacky approach and it might not work if someone overrides the FreeInstance method and does not call inherited. It also is not yet threadsafe. It also might have a small performance impact because of the dictionary access in every FreeInstance call.

But hey, nothing is for free, isn't it? ;)