如何在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编译器中,涉及释放对象有三个不同的阶段
- 调用
destructor Destroy
方法链 - 清理对象pipe理的字段 – string,接口,dynamic数组(在ARC编译器下也包含普通的对象引用)
- 释放堆中的对象内存
使用非ARC编译器释放对象
Component.Free
– >立即执行阶段1 -> 2 -> 3
用ARC编译器释放对象
-
Component.Free
或Component := nil
– >减less对象引用计数,然后是a)或b)- a)如果对象引用计数是0 – >立即执行阶段
1 -> 2 -> 3
- b)如果对象引用计数大于0,则不会发生任何其他事件
- a)如果对象引用计数是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
上有TEdit
和TPopupMenu
,并指定该popup菜单来编辑PopupMenu
属性,则编辑将在其FEditPopupMenu
字段中对popup式菜单进行强引用,并且popup式菜单将在其FFreeNotifies
列表中保留对编辑的强引用。 如果你想释放这两个组件中的任何一个,你必须调用DisposeOf
,否则它们将继续存在。
尽pipe您可以尝试手动跟踪这些连接,并在释放任何在实践中可能不那么容易的对象之前中断强引用周期。
下面的代码基本上泄露了ARC下的两个组件,因为它们将保持彼此的强引用,并且在过程完成之后,将不再有任何指向这两个组件之一的外部引用。 但是,如果将Menu.Free
replace为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