Search code examples
androiddelphifiremonkey

Freeing dynamically-created controls in Android


In my Firemonkey multi-platform app, I dynamically create some controls. After using them, I destroy them.

In Windows, there is no issue with this dynamically created and destroyed process. However, it seems that in Android, the code below does not destroy the controls as an error will popup to say that the Control already exists (and I do see the old controls are still there when I try to re-create the controls).

How do I ensure the controls are destroyed properly in Android (and/or iOS - I assume it's similar for iOS)?

for i := oParentRect.ControlsCount-1 downto 0 do
begin
  oControl := oParentRect.Controls[i];
  if (oControl is TText)
     or (oControl is TEdit)
     then
     begin           
       FreeAndNil( oControl );
       // I have also tried oControl.Free;
     end;
end;

Solution

  • Prior to RAD Studio 10.4, Delphi uses ARC (Automatic Reference Counting) for object lifetime management on iOS and Android platforms. Under ARC, TObject.Free() and FreeAndNil() do not behave as you are expecting. They are translated by the compiler into mere nil assignments, decrementing the object's reference count. An object is not freed until its reference count falls to 0.

    In your example, the object in question has multiple references (inside the Controls[] list, and in the oControl variable), so the FreeAndNil() does not have the desired effect since it is merely setting the oControl variable to nil and not removing the object from the Controls[] list. In other words, your example is effectively the same as the following:

    for i := oParentRect.ControlsCount-1 downto 0 do
    begin
      oControl := oParentRect.Controls[i]; // <-- increments refcnt
      if (oControl is TText) or (oControl is TEdit) then
      begin           
        //FreeAndNil( oControl );
        oControl := nil; // <-- decrements refcnt
      end;
    end;
    

    If you really want to destroy an object immediately when coding under ARC, you need to use TObject.DisposeOf() instead, eg:

    for i := oParentRect.ControlsCount-1 downto 0 do
    begin
      oControl := oParentRect.Controls[i]; // <-- increments refcnt
      if (oControl is TText) or (oControl is TEdit) then
      begin           
        //FreeAndNil( oControl );
        oControl.DisposeOf; // <-- destroys object, but does not free its memory yet
        oControl := nil; // <-- decrements refcnt
      end;
    end;
    

    Under this model, when an object is "disposed of", its destructor is called immediately, but its underlying memory block is not freed yet. Reference counting still occurs on the block. The object is in a "disposed" state until its reference count falls to 0, then the memory block is freed. The destructor is not called again.

    This behavior is documented on Embarcadero's DocWiki:The Free and Dispose Of methods under ARC

    Now, that being said, in RAD Studio 10.4, Embarcadero has removed object ARC handling completely, going back to the traditional memory management model (which they now refer to as "Unified Memory Management"). In which case, TObject.Free() and FreeAndNil() now behave the same as they always have on non-mobile platforms, but now on all platforms equally. So your original code will now work as expected, you do not need to switch to TObject.DisposeOf() in 10.4 onwards (though, you can if you want to, it will behave exactly like TObject.Free(), and give you the desired effect if you need to support 10.3 and earlier).