17

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.

bummi
  • 27,123
  • 14
  • 62
  • 101
michael mok
  • 171
  • 2
  • 3

2 Answers2

50

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;
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • 2
    Like David, I disagree that DisposeOf is the solution here. It is the most convenient one, and it probably works in most circumstances, but it is not the recommended way to do this. This answer promotes `DisposeOf` as some kind of standard solution. **It seems to completely be unaware of some of the possible implications.** This works right now, but it may one day bite you in the backside, producing hard to track problems in other parts of code. `DisposeOf` should only be used in exceptional circumstances. The proper way is to get rid of the references from Onwer and Parent and whatnot. – Rudy Velthuis Jan 15 '15 at 10:51
  • 4
    @Rudy `TComponent` descendants are mostly used in combination with `Forms`, `Frames` and `DataModules` in IDE designer. They can have convoluted relationships with other components and controls that will grab their references. Trying to resolve all that manually is Sisyphus work. There is a reason why FMX framework uses `DisposeOf` in releasing controls children and owned components. – Dalija Prasnikar Jan 15 '15 at 10:57
  • 5
    Making `TComponent` be ARC-aware WOULD NOT break any code at all. The problem is that on mobile, `TComponent` uses **strong references** to its owned components, and on desktop it uses **weak references** instead. All EMBT has to do is make `TComponent` use **weak references** on mobile, and then notifications work the same as on desktop, no `DisposeOf()` needed. – Remy Lebeau Jan 15 '15 at 17:17
  • 3
    Same with `TControl` and its child/parent controls. They need to be changed to **weak** as well. – Remy Lebeau Jan 15 '15 at 17:29
  • 5
    @Rudy please read quote I added from Allen Bauer's blog post. If that is not official recommendation to use `DisposeOf` on `TComponent` descendants, then I really don't know what else could satisfy you. – Dalija Prasnikar Jan 17 '15 at 09:40
  • I did read it, and he says: "the need to actively dispose of an instance on-demand was a mere fraction of the cases. In the vast majority (likely >90%) can simply let the system work. Especially in legacy code where the above discussed create..try..finally..free pattern is used. The need for calling DisposeOf explicitly is more the exception than the rule." – Rudy Velthuis Apr 26 '16 at 10:12
  • @RudyVelthuis Not when you deal with `TComponent` descendants. You may get away with not calling it, but as soon as some other component attaches to its notification system you are out of luck. And that can happen in code not directly under your control and in place where you cannot easily see connection. That makes such code fragile. – Dalija Prasnikar Apr 26 '16 at 10:19
  • If the component is about to be removed, it is very unlikely some other component will try to attach to the notification system. Components don't do that will nilly, there is usually a reason for it. – Rudy Velthuis Apr 26 '16 at 12:52
  • @RudyVelthuis I am not talking about moment when it will be removed but anywhere in between. From the moment it is created to the moment it needs to be released. – Dalija Prasnikar Apr 26 '16 at 12:54
  • Ok, but then there are other solutions. – Rudy Velthuis Apr 26 '16 at 13:07
  • 1
    @RudyVelthuis create TEdit control. then attach popup menu to it and bang, you have strong cycle. If you want to track all of them manually, be my guest. Chances are you will miss some and create nice memory leak. I don't like DisposeOf no more than you do, but it is here for a reason. – Dalija Prasnikar Apr 26 '16 at 13:11
14

On the mobile platforms lifetime is managed using ARC. Objects are only destroyed when there are no references to the object remaining. Your object has references to it, specifically from its parent.

Now you could use DisposeOf to force the object to be destroyed. More details here: http://blogs.embarcadero.com/abauer/2013/06/14/38948

However I suspect that a better solution would be to remove the references to the object. Remove it from its container. For instance by setting its parent to be nil.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Any Delphi component can have both a Parent and an Owner. The Parent will have a reference in it's Children list, the Owner will have one in it's Components list. I've not tested but I assume you'll need to remove it from both lists for ARC to free it. – Mike Sutton Jan 08 '15 at 20:42
  • @Mike I guess the way to deal with that would be not to give it an owner. `edit := TEdit.Create(nil)`. – David Heffernan Jan 08 '15 at 21:02
  • 1
    David, I would have given you more than one upvote, if I could. The long answer may look like it has all the answers, but like you, I think that using DisposeOf is the wrong solution, especially if it is promoted as some standard kind of handling this. – Rudy Velthuis Jan 15 '15 at 10:52
  • 6
    @MikeSutton and you add popup menu to edit control creating another strong reference cycle and then what? Break that cycle manually, too? You can go down that route if you like, but that is tedious and error prone work. As long as `TComponent` depends on notification system that breaks strong cycles in destructor `DisposeOf` is the only way to go. – Dalija Prasnikar Jan 15 '15 at 17:16
  • @DalijaPrasnikar I'm not really convinced that `DisposeOf` is any less error prone than "breaking a cycle manually". In fact it seems to me that `DisposeOf` is really just a more "brutal" way to break said cycles. I don't have any experience using Delphi's ARC. However, from what I've done in XCode, a key design consideration is determining which references should be strong and which should be weak. So only when an object loses all strong references can it be destroyed. Now if the Delphi framework is to blame for creating strong reference cycles: surely that's a bug for Embarcadero to fix? – Disillusioned Jan 29 '16 at 09:02
  • @CraigYoung Crux of the problem here is that DisposeOf is not regular ARC coding pattern. You don't have that in Objective-C or Swift. Problem is that ARC was imposed on frameworks and code written for different memory management. As long as you are dealing with classes that break strong cycles in destructor (perfectly valid for manual MM) you have to call DisposeOf on such classes. And there is no easy way to fix those classes under ARC and at the same time leave they behavior intact under non-ARC compilers. It is mission impossible. – Dalija Prasnikar Jan 29 '16 at 12:50