Is it leaking or not?
Catching memory leaks on the ARC compiler can be a daunting task. Even if the destructor of an object
instance is called, that still does not mean your object is completely released.
In the classic desktop compiler, a quick test to see whether some object instance was released or not was to add a breakpoint (or some logging code) into the destructor, and if you got there, all was good. This is not the case with the ARC compiler.
The last method called in the object instance lifecycle after which all instance memory will be completely released is
Fortunately,
The following class is broken in such a way that it creates a strong reference cycle with itself inside its constructor. This is the simplest example of an unbreakable strong reference cycle, where even
If you use the above class in the following manner, niling the object reference is not enough to trigger the destructor. The strong reference inside the object itself holds it alive. Neither
Example 1.
Using
Example 2.
To completely release our
The same would happen if we mark the
There is also another way, less ARC friendly, and that is clearing the strong reference cycle inside dthe estructor, by explicitly clearing the
In the classic desktop compiler, a quick test to see whether some object instance was released or not was to add a breakpoint (or some logging code) into the destructor, and if you got there, all was good. This is not the case with the ARC compiler.
The last method called in the object instance lifecycle after which all instance memory will be completely released is
TObject.FreeInstance
. While this method is common to both ARC and
classic compilers, on the classic compiler executing the destructor would inevitably (unless
it throws an uncaught exception) mean executing FreeInstance
and completing the release
cycle. On the ARC compiler, there is no such guarantee. The destructor can also be called
directly via DisposeOf
, and FreeInstance
will be called only after all strong references
to the object are cleared. Since calling the destructor does not imply clearing of all strong
references FreeInstance
may or may not run. If it does not run - you have a memory leak. Fortunately,
FreeInstance
is also a virtual method, which means it can be easily overriden. Of
course, you can also use TVirtualMethodInterceptor
and intercept the FreeInstance
method for a
particular object instance, but overriding FreeInstance
is the fastest way to confirm
the memory leak. Of course, if you already have your leaking object suspect. The following class is broken in such a way that it creates a strong reference cycle with itself inside its constructor. This is the simplest example of an unbreakable strong reference cycle, where even
DisposeOf
will not help. It will merely call the destructor of the object,
but the memory allocated on the heap will never be released.
type
TFoo = class(TObject)
public
Strong: TFoo;
constructor Create;
destructor Destroy; override;
procedure FreeInstance; override;
end;
constructor TFoo.Create;
begin
Strong := Self;
end;
destructor TFoo.Destroy;
begin
Log.d('FOO DESTROY');
inherited;
end;
procedure TFoo.FreeInstance;
begin
Log.d('FOO FREEINSTANCE');
inherited;
end;
If you use the above class in the following manner, niling the object reference is not enough to trigger the destructor. The strong reference inside the object itself holds it alive. Neither
Destroy
nor FreeInstance
will be executed. Example 1.
var
foo: TFoo;
begin
foo := TFoo.Create;
foo := nil;
end;
Using
DisposeOf
instead of niling helps a bit, but it will only take you so far. It will only
call the destructor, but not FreeInstance
. If you have such a case where FreeInstance
is never called on an object, that means the object is leaking.Example 2.
var
foo: TFoo;
begin
foo := TFoo.Create;
foo.DisposeOf;
end;
To completely release our
foo
object instance, we have to fix the code. There are several ways
to do so. First, the most obvious one is not to take a strong reference to Self. Commenting out
that code in the constructor fixes the code and FreeInstance
will be called in both of the above examples.The same would happen if we mark the
Strong
reference inside the TFoo
class with the [weak]
or [unsafe]
attribute.
type
TFoo = class(TObject)
public
[weak] Strong: TFoo;
There is also another way, less ARC friendly, and that is clearing the strong reference cycle inside dthe estructor, by explicitly clearing the
Strong
reference. However, in that case you must
use DisposeOf
to force execution of the destructor code. Merely niling the object like in the first
example will not help.
destructor TFoo.Destroy;
begin
Log.d('FOO DESTROY');
Strong := nil;
inherited;
end;
Comments
Post a Comment