Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to free a component in Android / iOS

I dynamically create a TEdit on a form in Android:

edit := TEdit.Create(Self);

I want to free it using edit.Free, but it just still on form.

This code works fine on win32, but failed on Android.

The same seems to happen not only for TEdit but for any component using Android or iOS.

like image 587
michael mok Avatar asked Jan 07 '15 11:01

michael mok


1 Answers

Update for 10.4

Delphi 10.4 Sydney unified memory management across all platforms and removed ARC compiler. In other words, all platforms now follow the same memory management rules as the Windows platform.

DisposeOf vs Free in classic (non ARC) compiler

  • DisposeOf on classic compiler calls Free and functionally behaves the same
  • DisposeOf is left for backward compatibility only, in new code (that does not have to keep compatibility with ARC compilers) using Free is preferred
  • In existing code DisposeOf does not have do be changed to Free

Original answer, valid for ARC compilers:

Short Answer

There are two rules that should be followed when releasing any TComponent descendant object under Delphi ARC compilers (currently Android and iOS):

  • using DisposeOf is mandatory regardless of object having owner or not
  • in destructors or in cases where reference is not going out of scope shortly after DisposeOf is called, object reference should also be set to nil (detailed explanation in Pitfalls)

It may be appealing to have DisposeOfAndNil method, but ARC makes it far more complicated than it was the case with old FreeAndNil method and I would suggest using plain DisposeOf - nil sequence to avoid additional issues:

Component.DisposeOf;
Component := nil;

While in many cases code will function properly even if above rules are not followed, such code would be rather fragile and could easily be broken by other code introduced in seemingly unrelated places.

DisposeOf in context of ARC memory management

DisposeOf breaks ARC. It violates golden rule of ARC Any object reference can be either valid object reference or nil and introduces third state - disposed "zombie" object reference.

Anyone trying to understand ARC memory management should look at DisposeOf like addition that just solves Delphi specific framework issues and not concept that really belongs to ARC itself.

Why DisposeOf exists in Delphi ARC compilers?

TComponent class (and all its descendants) was designed with manual memory management in mind. It uses notification mechanism that is not compatible with ARC memory management because it relies on breaking strong reference cycles in destructor. Since TComponent is one of base classes Delphi frameworks rely upon, it must be able to function properly under ARC memory management.

Besides Free Notification mechanism there are other similar designs in Delphi frameworks suitable for manual memory management because they rely on breaking strong reference cycles in destructor, but those designs are not suitable for ARC.

DisposeOf method enables direct calling of object destructor and enables such legacy code to play along with ARC.

One thing must be noted here. Any code that uses or inherits from TComponent automatically becomes legacy code in context of proper ARC management even if you write it today.

Quote from Allen Bauer's blog Give in to the ARC side

So what else does DisoseOf solve? It is very common among various Delphi frameworks (VCL and FireMonkey included), to place active notification or list management code within the constructor and destructor of a class. The Owner/Owned model of TComponent is a key example of such a design. In this case, the existing component framework design relies on many activities other than simple “resource management” to happen in the destructor.

TComponent.Notification() is a key example of such a thing. In this case, the proper way to “dispose” a component, is to use DisposeOf. A TComponent derivative isn’t usually a transient instance, rather it is a longer-lived object which is also surrounded by a whole system of other component instances that make up things such as forms, frames and datamodules. In this instance, use DisposeOf is appropriate.

How DisposeOf works

To have better understanding of what exactly happens when DisposeOf is called, it is necessary to know how Delphi object destruction process works.

There are three distinct stages involved in releasing object in both ARC and non-ARC Delphi compilers

  1. calling destructor Destroy methods chain
  2. cleaning up object managed fields - strings, interfaces, dynamic arrays (under ARC compiler that includes plain object references, too)
  3. releasing object memory from the heap

Releasing object with non-ARC compilers

Component.Free -> immediate execution of stages 1 -> 2 -> 3

Releasing object with ARC compilers

  • Component.Free or Component := nil -> decreases object reference count followed by a) or b)

    • a) if object reference count is 0 -> immediate execution of stages 1 -> 2 -> 3
    • b) if object reference count is greater than 0 nothing else happens

  • Component.DisposeOf -> immediate execution of stage 1, stages 2 and 3 will be executed later when objects reference count reaches 0. DisposeOf does not decrease reference count of calling reference.

TComponent notification system

TComponent Free Notification mechanism notifies registered components that particular component instance is being freed. Notified components can handle that notification inside virtual Notification method and make sure that they clear all references they may hold on component being destroyed.

Under non-ARC compilers that mechanism ensures that you don't end up with dangling pointers pointing to invalid - released objects and under ARC compilers clearing references to destroying component will decrease its reference count and break strong reference cycles.

Free Notification mechanism is being triggered in TComponent destructor and without DisposeOf and direct execution of destructor, two components could hold strong references to each other keeping themselves alive during whole application lifetime.

FFreeNotifies list that holds list of components interested in notification is declared as FFreeNotifies: TList<TComponent> and it will store strong reference to any registered component.

So for instance if you have TEdit and TPopupMenu on your form and assign that popup menu to edit's PopupMenu property, edit will hold strong reference to popup menu in its FEditPopupMenu field, and popup menu will hold strong reference to edit in its FFreeNotifies list. If you want to release either of those two components you have to call DisposeOf on them or they will just continue to exist.

While you can try to track those connections manually and break strong reference cycles before you release any of those objects that may not be so easy to do in practice.

Following code will basically leak both components under ARC because they will hold strong reference to each other, and after procedure is finished you will no longer have any external references that point to either one of those components. However, if you replace Menu.Free with Menu.DisposeOf you will trigger Free Notification mechanism and break strong reference cycle.

procedure ComponentLeak;
var
  Edit: TEdit;
  Menu: TPopupMenu; 
begin
  Edit := TEdit.Create(nil);
  Menu := TPopupMenu.Create(nil);

  Edit.PopupMenu := Menu; // creating strong reference cycle

  Menu.Free; //  Menu will not be released because Edit holds strong reference to it
  Edit.Free; // Edit will not be released because Menu holds strong reference to it
end;

Pitfalls of DisposeOf

Besides breaking ARC, that is bad on its own, because when you break it you don't have much use of it, there are also two major issues with how DisposeOf is implemented that developers should be aware of.

1. DisposeOf does not decrease reference count on calling reference QP report RSP-14681

type
  TFoo = class(TObject)
  public
    a: TObject;
  end;

var
  foo: TFoo; 
  b: TObject;

procedure DoDispose;
var
  n: integer;
begin
  b := TObject.Create;
  foo := TFoo.Create;
  foo.a := b;
  foo.DisposeOf;
  n := b.RefCount; // foo is still alive at this point, also keeping b.RefCount at 2 instead of 1
end;

procedure DoFree;
var
  n: integer;
begin
  b := TObject.Create;
  foo := TFoo.Create;
  foo.a := b;
  foo.Free;
  n := b.RefCount; // b.RefCount is 1 here, as expected 
end;

2. DisposeOf does not clean up instance inner managed types references QP report RSP-14682

type
  TFoo = class(TObject)
  public
    s: string;
    d: array of byte;
    o: TObject;
  end;

var
  foo1, foo2: TFoo;

procedure DoSomething;
var
  s: string;
begin
  foo1 := TFoo.Create;
  foo1.s := 'test';
  SetLength(foo1.d, 1);
  foo1.d[0] := 100;
  foo1.o := TObject.Create;
  foo2 := foo1;
  foo1.DisposeOf;
  foo1 := nil;
  s := IntToStr(foo2.o.RefCount) + ' ' + foo2.s + ' ' + IntToStr(foo2.d[0]); 
  // output: 1 test 100 - all inner managed references are still alive here, 
  // and will live until foo2 goes out of scope
end;

workaround

destructor TFoo.Destroy;
begin
  s := '';
  d := nil;
  o := nil;
  inherited;
end;

Combined effect of above issues can manifest itself in different manners. From keeping more allocated memory than necessary to hard to catch bugs that are caused by wrong, unexpected reference count of contained non-owned object and interface references.

Since DisposeOf does not decrease reference count of calling reference it is important to nil such reference in destructors otherwise whole object hierarchies can stay up alive much longer than needed and in some cases even during the whole application lifetime.

3. DisposeOf cannot be used to resolve all circular references

Last, but not least issue with DisposeOf is that it will break circular references only if there is code in destructor that resolves them - like TComponent notification system does.

Such cycles that are not handled by destructor should be broken using [weak] and/or [unsafe] attributes on one of the references. That is also preferred ARC practice.

DisposeOf should not be used as quick fix for breaking all reference cycles (the ones it was never designed for) because it will not work and abusing it can result in hard to track memory leaks.

Simple example of cycle that will not be broken by DisposeOf is:

type
  TChild = class;

  TParent = class(TObject)
  public
    var Child: TChild;
  end;

  TChild = class(TObject)
  public
    var Parent: TParent;
    constructor Create(AParent: TParent);
  end;

constructor TChild.Create(AParent: TParent);
begin
  inherited Create;
  Parent := AParent;
end;

var
  p: TParent;
begin
  p := TParent.Create;
  p.Child := TChild.Create(p);
  p.DisposeOf;
  p := nil;
end;

Above code will leak both child and parent object instances. Combined with the fact that DisposeOf does not clear inner managed types (including strings) those leaks can be huge depending on what kind of data you are storing inside. The only (proper) way to break that cycle is by changing TChild class declaration:

  TChild = class(TObject)
  public
    [weak] var Parent: TParent;
    constructor Create(AParent: TParent);
  end;
like image 133
Dalija Prasnikar Avatar answered Oct 07 '22 03:10

Dalija Prasnikar