Today’s post is inspired from a question I answered on stackoverflow a while ago. I’m bringing you some important point to consider when using interface.
First, one thing worth mentioning is that interface are not necessarily reference counted. For example, TComponent implements IInterface, but if we look at it’s AddRef and Release methods, they both returns -1 indicating no reference counting occurs. TInterfacedObject, on the other hand, does take care of the reference counting. This is an important distinction because it affects how we should use interface.
When using those, it’s usually preferable to avoid keeping a reference to the implementing object as we have no control on the lifetime of the object, the object will get destroyed at the same time it’s last interface reference is cleared.
If we absolutely need to keep the object reference around, we have 2 options.
- Keep an interface variable alongside the object variable to keep the object alive.
TMyClass = class(TInterfaceObject); [...] TForm1 = class(TForm) FObject : TMyClass; FObjectIntf : IUnknown; public [...] //Creating the object FObject := TMyClass.Create; FObjectIntf := FObject; //Destroying the object FObject := nil; //Do NOT free the object FObjectIntf := nil; //FObject's Destructor is called here.
- Use a FreeNotification mechanism to set the object’s variable back to nil when it is freed. This is built-in TComponent. Even though our class isn’t derived from TComponent, we can still leverage the functionality like this :
TMyClass = class(TInterfaceObject) FNotifier : TComponent [...] TForm1 = class(TForm) private FObject : TMyClass; protected procedure Notification(AComponent: TComponent; Operation: TOperation);override; [...] procedure TForm1.Notification(AComponent: TComponent; Operation: TOperation); begin inherited; if FObject.FNotifier = FComponent then FObject := nil; end; //Creating the object FObject := TMyClass.Create; Form1.FreeNotification(FObject.FNotifier); //Destroying the object FObject.Free; //Here, FObject is assigned. Assert(FObject = nil); //Here, FObject is not assigned anymore, it was set to "nil" in TForm1.Notification.
When working with reference counted interface, it is worth nothing that, if 2 interfaces reference each others, they are never going to be freed. This is called “Circular Reference”. This is also seen with Automatic Reference Counting (ARC). The best method to break circular reference is a subject I still have to research (Especially in regard to the new [weak] keyword) so I won’t make any suggestion here, but I thought it was worth mentioning.
Non reference-counted interface
Here, we need to keep an object reference around to be able to free our object’s memory as it won’t be freed through reference counting and, unless it implements IDisposable or a similar interface, we can’t free it from the interface reference. Thus, we have the opposite problem. We are unable to determine if there are references to our object left.
That being said, the problem isn’t specific to interface. The same problem would occur if we would pass the reference to our object around as a simple object. Since this post is more about interfaces, I won’t delve into it here (though I might write about it some other time). But, one of the ways to make sure no references are left to our object is (*drum roll*), FreeNotification.
In which circumstance would we want to use interface without the reference counting? Interfaces are tools and a tool’s usefulness is mostly limited by one’s imagination. But one I can name on the top of my head is using it as a “method binding” technique or pseudo “Duck Typing“. One such example can be seen in one of my previous post here. Interface is also pretty much how anonymous methods are implemented “behind the scene”. I’m sure you guys can figure out clever usage for them!