mic*_*mok 13 delphi android components ios firemonkey
我TEdit在Android上动态创建表单:
edit := TEdit.Create(Self);
Run Code Online (Sandbox Code Playgroud)
我想用它来释放它edit.Free,但它仍然在形式上.
此代码在win32上运行正常,但在Android上失败.
同样的情况似乎不仅发生在TEdit上,也发生在任何使用Android或iOS的组件上.
Dal*_*kar 44
TComponent在Delphi ARC编译器(目前是Android和iOS)下发布任何后代对象时,应遵循两条规则:
DisposeOf无论对象是否拥有,使用都是强制性的DisposeOf调用之后不久引用不会超出范围的情况下,对象引用也应该设置为nil(陷阱中的详细说明)使用DisposeOfAndNil方法可能很有吸引力,但ARC使它比旧FreeAndNil方法的情况复杂得多,我建议使用普通DisposeOf - nil序列来避免其他问题:
Component.DisposeOf;
Component := nil;
Run Code Online (Sandbox Code Playgroud)
虽然在许多情况下,即使不遵循上述规则,代码也能正常运行,但这样的代码会相当脆弱,并且很容易被看似无关的地方引入的其他代码破坏.
DisposeOf打破ARC.它违反了ARC的黄金法则任何对象引用都可以是有效的对象引用或者是nil,并引入第三个状态 - 处理的"僵尸"对象引用.
任何试图理解ARC内存管理的人都应该看一下DisposeOf只能解决Delphi特定框架问题的附加内容,而不是真正属于ARC本身的概念.
TComponentclass(及其所有后代)的设计考虑了手动内存管理.它使用与ARC内存管理不兼容的通知机制,因为它依赖于破坏析构函数中的强引用循环.由于TComponent是Delphi框架所依赖的基类之一,它必须能够在ARC内存管理下正常运行.
除了Free Notification机制外,Delphi框架中还有其他类似的设计适用于手动内存管理,因为它们依赖于在析构函数中打破强大的参考周期,但这些设计不适合ARC.
DisposeOf方法可以直接调用对象析构函数,并使这些遗留代码能够与ARC一起使用.
这里必须注意一件事.即使您今天编写,任何使用或继承的代码都会在正确的ARC管理环境中TComponent自动成为遗留代码.
引用Allen Bauer的博客Give in ARC一侧
那么DisoseOf还能解决什么呢?在各种Delphi框架(包括VCL和FireMonkey)中,将活动通知或列表管理代码放在类的构造函数和析构函数中是很常见的.TComponent的所有者/所有者模型是这种设计的关键示例.在这种情况下,现有的组件框架设计依赖于除了在析构函数中发生的简单"资源管理"之外的许多活动.
TComponent.Notification()是这种事情的一个关键例子.在这种情况下,"处理"组件的正确方法是使用DisposeOf.TComponent派生词通常不是瞬态实例,而是一个寿命较长的对象,它也被构成表单,框架和数据模块等其他组件实例的整个系统所包围.在这种情况下,使用DisposeOf是合适的.
为了更好地理解DisposeOf调用时究竟发生了什么,有必要了解Delphi对象销毁过程的工作原理.
在ARC和非ARC Delphi编译器中发布对象涉及三个不同的阶段
destructor Destroy方法链使用非ARC编译器释放对象
Component.Free - >立即执行阶段 1 -> 2 -> 3
使用ARC编译器释放对象
Component.Free或Component := nil- >减少对象引用计数,然后a)或b)
1 -> 2 -> 3Component.DisposeOf- >立即执行阶段1,阶段,2并3在对象引用计数达到0时DisposeOf执行.不会减少调用引用的引用计数.
TComponent通知系统
TComponent Free Notification机制通知已注册的组件正在释放特定组件实例.通知的组件可以在虚拟Notification方法中处理该通知,并确保它们清除它们可能在被销毁的组件上保留的所有引用.
在非ARC编译器下,该机制确保您不会最终指向指向无效释放对象的悬空指针,并且在ARC编译器下清除对销毁组件的引用将减少其引用计数并打破强引用周期.
Free Notification机制是在TComponent析构函数中触发的,没有DisposeOf和直接执行析构函数,两个组件可以在整个应用程序生命周期中保持对彼此的强大引用.
FFreeNotifies保存对通知感兴趣的组件列表的列表被声明为FFreeNotifies: TList<TComponent>,它将存储对任何已注册组件的强引用.
因此,例如,如果您拥有TEdit并TPopupMenu在您的表单上并将该弹出菜单指定为编辑PopupMenu属性,则编辑将在其FEditPopupMenu字段中保留对弹出菜单的强引用,并且弹出菜单将在其FFreeNotifies列表中保留对编辑的强引用.如果要释放这两个组件中的任何一个,则必须调用DisposeOf它们,否则它们将继续存在.
虽然您可以尝试手动跟踪这些连接并在释放任何在实践中可能不那么容易的对象之前打破强引用周期.
以下代码基本上会泄漏ARC下的两个组件,因为它们将保持对彼此的强引用,并且在过程完成后,您将不再有任何外部引用指向其中任何一个组件.但是,如果您更换Menu.Free同Menu.DisposeOf你会触发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;
Run Code Online (Sandbox Code Playgroud)
除了打破ARC之外,这本身就很糟糕,因为当你打破它时你没有太多使用它,DisposeOf开发人员应该注意的实现方式也存在两个主要问题.
1. DisposeOf不会减少调用参考 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;
Run Code Online (Sandbox Code Playgroud)
2. DisposeOf不清理实例内部托管类型引用 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;
Run Code Online (Sandbox Code Playgroud)
解决方法
destructor TFoo.Destroy;
begin
s := '';
d := nil;
o := nil;
inherited;
end;
Run Code Online (Sandbox Code Playgroud)
上述问题的综合效应可以以不同的方式表现出来.从保持更多分配的内存到难以捕获由包含的非拥有对象和接口引用的错误,意外引用计数引起的错误.
由于DisposeOf不减少调用引用的引用计数,因此nil在析构函数中对此类引用很重要,否则整个对象层次结构可以比所需的更长时间保持活动,并且在某些情况下甚至在整个应用程序生命周期中.
3. DisposeOf不能用于解决所有循环引用
最后,但并非最不重要的问题DisposeOf是,只有在析构函数中有解析它们的代码时才会破坏循环引用 - 就像TComponent通知系统那样.
这些不由析构函数处理的循环应该使用其中一个引用的[weak]和/或[unsafe]属性来破坏.这也是ARC的首选做法.
DisposeOf不应该用作快速修复来打破所有参考周期(它从未设计过的参考周期),因为它不起作用并且滥用它会导致难以跟踪内存泄漏.
不会被破坏的循环的简单示例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;
Run Code Online (Sandbox Code Playgroud)
上面的代码将泄漏子对象实例和父对象实例.结合DisposeOf不清除内部托管类型(包括字符串)的事实,这些泄漏可能很大,具体取决于您存储在内部的数据类型.打破这个循环的唯一(正确)方法是改变TChild类声明:
TChild = class(TObject)
public
[weak] var Parent: TParent;
constructor Create(AParent: TParent);
end;
Run Code Online (Sandbox Code Playgroud)
Dav*_*nan 11
在移动平台上,使用ARC管理生命周期.只有在没有对象剩余的引用时才会销毁对象.您的对象具有对它的引用,特别是来自其父对象.
现在你可以使用DisposeOf强制对象被销毁.更多详情:http://blogs.embarcadero.com/abauer/2013/06/14/38948
但是我怀疑更好的解决方案是删除对象的引用.将其从容器中取出.例如,通过将其父级设置为nil.
| 归档时间: |
|
| 查看次数: |
6433 次 |
| 最近记录: |