Checking Object Lifetimes with
AUTOREFCOUNT

AutoRefCount is mandatory with the new generation compiler, this theoretically removes the obligation and necessity of managing object lifetimes. {ARC} However for any system which has any sort of complex object relationships this does not work and the task of making sure objects are freed becomes more difficult with ARC than without it.


It is often useful for objects to have a reference to their parent for example

TMyParent = class(TObject)
  TParent:TMyParent; 
  FListOfChildren:TList<TMyChild>
  FListOfOthers:TList<TMyParent>
end;

TMyChild = class(TObject)
  TParent:TMyParent; 
end;

Now we can easily walk up the object tree to find where I came from but AutoRefCount is broken and objects may never be released. With NextGen Freeing an Object only nulls the local reference. Normally Destroy is not called until all references are removed. The problem above can be simply fixed by creating a week reference [Weak]

[Weak] TParent:TMyParent; 

To me this seems just as problematic as remembering (and understanding) the insertion of the Try Finally blocks this “enhancement” was meant to fix and if you fail to do it correctly your objects remain.

While testing on Windows I use

System.ReportMemoryLeaksOnShutdown:=true;

This tells me if I failed to manage my object lifetime and because the same object life time management system is used on OSX exes I assume all is well there too. Not so with mobile deployments.

TObject now has a new method DisposeOf which ensures Destroy is called at a managed time but while the Destroy function can be used to nil any references held by this object you cannot be sure the objects are actually released.

Tracking Object Creation and Actual Release

In my D-Unit Testing I used to check that object lifetimes are correctly managed by incrementing a counter on each Create and decrementing on each Destroy for my class under test. This does not work under ARC so I now decrement in FreeInstance by adding an override in my base Business Objects.


{$IFDEF DEBUG}
    Procedure FreeInstance; override;
{$ENDIF}

//And in Implementation

Constructor TMyObject.Create({Parameters});
begin
  Inherited;
  //Code Lines
{$IFDEF DEBUG}
  IncObjectCount(Self);
{$ENDIF}
end;
 
{$IFDEF DEBUG}
procedure TMyObject.FreeInstance;
begin
  inherited;
  DecObjectCount(Self);
end;
{$ENDIF}

This requires the inclusion of “ISObjectCounter” in the uses clause and a copy of the library code unit ISObjectCounter.pas in the search path. ISObjectCounter.pas is available via Github and provides a number of aditional feature you may find useful.

The basic calls to

  
Procedure IncObjectCount(AObj:Pointer);
Procedure DecObjectCount(AObj:Pointer);

initially just increment and decrement a counter the value of which can be accessed at any time via

Function CurrentObjectCount: Integer;

However if you call

Procedure TrackObjectTypes;

then pointers to the current objects will be saved so that additional functions can obtain a list of the class names of object not yet removed from the list (Released and Memory Recovered) as colon separated string or as an “array of string”. This can often give an indication of why objects are not released.

Function ReportObjectTypes: TCtrReportArray;
Function ObjectTypesAsString: String;

The library module “ISObjectCounter” was a result of porting a large amount of existing code to Mobile Devices and ensuring the Unit tests still succeeded. It became obvious that Free and FreeAndNil on the mobile environment was now effectively meaningless and if I wanted make sure that Destroy was called on my complex object trees enabling the contained object to be released I need to change Free to DisposeOf and replace FreeAndNil with an new DisposeOfAndNil. DisposeOfAndNil is provided in the library as are other useful functions to check the value of the reference counter and state of the In Dispose and After Dispose Flags.

Procedure DisposeOfAndNil(Var AObj: TObject);
Function DecodeInDispose(AObj: TObject): Boolean;
Function DecodeAfterDispose(AObj: TObject): Boolean;
Function DecodeRefCount(AObj: TObject): Integer;

Unfortunately the Object parameter in DisposeOfAndNil has to be Typecast to (TObject(MyObj)) to pass the compiler check as it is a Var. This could possible be fixed with a new definition of

Procedure DisposeOfAndNil(Var AObj: Pointer);

But as this would change the hidden reference count handling of the parameters such a change would need extensive testing.

Source code breakpoints can also be set in IncObjectCount / DecObjectCount which lets you easily view the current reference count.

{$IFDEF DEBUG}
{$IFDEF AutoRefCount}
  if AObj <> nil then
    Count := TObject(AObj).RefCount;
{$ENDIF}
{$ENDIF}

Leave a Reply

Your email address will not be published.