如何在Android / iOS中释放组件

我在Android的表单上dynamic创build一个TEdit

 edit := TEdit.Create(Self); 

我想使用edit.Free释放它,但它仍然在表单上。

此代码在win32上正常工作,但在Android上失败。

不仅对于TEdit,而且对于使用Android或iOS的任何组件,似乎也是如此。

简答

在Delphi ARC编译器(当前为Android和iOS)下释放任何TComponent后代对象时,应遵循两条规则:

  • 不pipe对象是否拥有者,使用DisposeOf都是强制的
  • 在析构函数中,或者在调用DisposeOf后不久,引用不会超出范围的情况下,对象引用也应该设置nil (陷阱中的详细解释)

DisposeOfAndNil方法可能会有吸引力,但ARC使得它比旧的FreeAndNil方法更复杂,我build议使用普通的DisposeOf - nil序列来避免其他问题:

 Component.DisposeOf; Component := nil; 

尽pipe在很多情况下,即使没有遵循上述规则,代码也会正常运行,但是这样的代码会非常脆弱,并且很容易被在看起来不相关的地方引入的其他代码所破坏。

DisposeOf在ARC内存pipe理的上下文中

DisposeOf打破ARC。 它违反了ARC的黄金法则。 任何对象引用都可以是有效的对象引用,也可以是零,并引入了第三种状态的“僵尸”对象引用。

任何试图理解ARC内存pipe理的人都应该看看DisposeOf ,就像解决Delphi特定的框架问题,而不是真正属于ARC本身的概念。

为什么在Delphi ARC编译器中存在DisposeOf?

TComponent类(及其所有后代)在devise时都考虑了手动内存pipe理。 它使用与ARC内存pipe理不兼容的通知机制,因为它依赖于在析构函数中打破强引用周期。 由于TComponent是Delphi框架所依赖的基类之一,它必须能够在ARC内存pipe理下正常运行。

除了Free Notification机制之外,Delphi框架中还有其他类似的devise,适合于手动内存pipe理,因为它们依赖于在析构函数中破坏强引用周期,但这些devise不适合ARC。

DisposeOf方法允许直接调用对象析构函数,并使这些遗留代码与ARC一起玩。

有一点必须注意这里。 任何使用或从TComponent inheritance的代码都会在正确的ARCpipe理环境中自动变成遗留代码 ,即使您今天编写代码

引用来自Allen Bauer的博客给予ARC一面

那么DisoseOf还能解决什么问题? 在各种Delphi框架(包括VCL和FireMonkey)中,这是非常常见的,在一个类的构造函数和析构函数中放置活动的通知或列表pipe理代码。 TComponent的Owner / Owned模型就是这样一个devise的一个关键例子。 在这种情况下,现有的组件框架devise依赖于除了简单的“资源pipe理”在析构函数中发生的许多活动。

TComponent.Notification()是这样一个关键的例子。 在这种情况下,“处理”组件的正确方法是使用DisposeOf。 TComponent衍生物通常不是一个暂时的实例,而是一个长寿命的对象,它也被其他组件实例的整个系统所包围,这些实例组成了诸如窗体,框架和数据模块之类的东西。 在这种情况下,使用DisposeOf是适当的。

DisposeOf如何工作

为了更好地理解在DisposeOf时究竟发生了什么,有必要知道Delphi对象销毁过程是如何工作的。

在ARC和非ARC的Delphi编译器中,涉及释放对象有三个不同的阶段

  1. 调用destructor Destroy方法链
  2. 清理对象pipe理的字段 – string,接口,dynamic数组(在ARC编译器下也包含普通的对象引用)
  3. 释放堆中的对象内存

使用非ARC编译器释放对象

Component.Free – >立即执行阶段1 -> 2 -> 3

用ARC编译器释放对象

  • Component.FreeComponent := nil – >减less对象引用计数,然后是a)b)

    • a)如果对象引用计数是0 – >立即执行阶段1 -> 2 -> 3
    • b)如果对象引用计数大于0,则不会发生任何其他事件
  • 当对象引用计数达到0时, Component.DisposeOf – >阶段1阶段2和阶段3即时执行将稍后执行DisposeOf不会减less调用参考的引用计数。

TComponent通知系统

TComponent Free Notification机制通知已注册的组件正在释放特定的组件实例。 通知的组件可以在虚拟Notification方法中处理该通知,并确保它们清除所有可能被销毁的组件的引用。

在非ARC编译器的机制下,这种机制可以确保你最终没有悬挂着指向无效释放对象的指针,而且在ARC编译器中,清除对组件的引用将会减less引用计数并且破坏强引用周期。

Free Notification机制在DisposeOf析构函数中被触发,没有DisposeOf和直接执行析构函数,两个组件可以在整个应用程序生命周期中保持彼此的强引用。

FFreeNotifies包含对通知感兴趣的组件列表的列表被声明为FFreeNotifies: TList<TComponent> ,它将存储对任何注册组件的强引用。

因此,例如,如果您的TPopupMenu上有TEditTPopupMenu ,并指定该popup菜单来编辑PopupMenu属性,则编辑将在其FEditPopupMenu字段中对popup式菜单进行强引用,并且popup式菜单将在其FFreeNotifies列表中保留对编辑的强引用。 如果你想释放这两个组件中的任何一个,你必须调用DisposeOf ,否则它们将继续存在。

尽pipe您可以尝试手动跟踪这些连接,并在释放任何在实践中可能不那么容易的对象之前中断强引用周期。

下面的代码基本上泄露了ARC下的两个组件,因为它们将保持彼此的强引用,并且在过程完成之后,将不再有任何指向这两个组件之一的外部引用。 但是,如果将Menu.Freereplace为Menu.Free ,则将触发Free Notification机制并中断强参考周期。

 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; 

DisposeOf的缺陷

除了打破ARC之外,这样做本身就是不好的,因为当你打破它的时候,你没有太多的用处,关于DisposeOf的实现方式,开发者应该注意的还有两个主要的问题。

1. DisposeOf不会减less调用参考 QP报告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不清理实例内部pipe理的types引用 QP报告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; 

解决方法

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

上述问题的综合影响可以以不同的方式performance出来。 从保留更多的分配内存不必要到难以捕捉错误,包含非拥有对象和接口引用的意外引用计数引起的错误。

由于DisposeOf不会减less调用引用的引用计数,所以在析构函数中DisposeOf这样的引用是非常重要的,否则整个对象层次结构可能会比需要的更长久地存活,甚至在整个应用程序生命周期中也会存在。

3. DisposeOf不能用来解决所有的循环引用

最后, DisposeOf问题是,只有当析构函数中有parsing它们的代码时,它才会中断循环引用 – 就像TComponent通知系统一样。

这些不被析构函数处理的周期应该在其中一个引用上使用[weak]和/或[unsafe]属性来打破。 这也是ARC的惯例。

DisposeOf不应该被用作快速修复所有引用循环(它从来没有被devise过的),因为它不会工作,滥用它会导致很难跟踪内存泄漏。

DisposeOf不会被破坏的简单循环示例是:

 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; 

上面的代码会泄漏子对象和父对象实例。 结合DisposeOf不清除内部托pipetypes(包括string)的事实,这些泄漏可能是巨大的,这取决于您在里面存储什么样的数据。 唯一的(适当的)打破这个循环的方法是改变TChild类的声明:

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

在移动平台上,使用ARCpipe理生命周期。 对象只有在没有对象的引用时才被销毁。 你的对象有它的引用,特别是从它的父对象。

现在你可以使用DisposeOf强制对象被销毁。 更多细节在这里: http : //blogs.embarcadero.com/abauer/2013/06/14/38948

不过我怀疑,更好的解决scheme是删除对象的引用。 将其从容器中取出。 例如通过设置其父项为零。

我已经尝试了以上所有,并没有得到任何..我的最后一个select可能会导致一串仇恨评论,但唯一可能的解决scheme,为我工作到底..

我有一个滚动框,添加我自己制作的组件(从TPanel派生) – 大多数是50个。 我最好的解决办法是:

在创build第二层物品之前:

 Scrollbox1.align := TAlignLayout.none; Scrollbox1.width := 0; Scrollbox1.position.y := 5000; scrollbox1.visible := false; scrollbox1.name := 'Garbage' + inttostr(garbageitemscount); garbageitemscount := garbageitemscount + 1; scrollbox1 := TScrollbox.create(nil); scrollbox1.parent := ContainerPanel; Scrollbox1.align := TAlignLayout.client; 

当创build组件,他们的父母被设置为Scrollbox1再次..这给人的幻想,项目已经消失,实际上他们只是移动到屏幕上,直到应用程序closures,然后他们被Android释放..

我不能认为这种方法对大规模应用来说是完全可行的,但最终适合我的需求。对于那些也不知道该怎么做的人来说。 Component.DisposeOf – 崩溃的应用程序Component.Free – 无论如何

事实上,根据我的申请:

 Component := TComponent.create(ScrollBox1); Component.Parent := ScrollBox1; showmessage(inttostr(ScrollBox1.ChildrenCount)); //<-- This says 0, even tho 50 Components are present