如何在Android / iOS中释放组件 [英] How to free a component in Android / iOS

查看:180
本文介绍了如何在Android / iOS中释放组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在Android中的表单上动态地创建了一个 TEdit

 编辑:= TEdit.Create(Self); 

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



此代码在win32上运行正常,但在Android上失败。



同样的事情似乎不仅发生在TEdit,而且使用Android或iOS的任何组件。

解决方案

简答

在Delphi ARC编译器(目前为Android和iOS)下发布任何 TComponent descendant对象时,应遵循两个规则: $ b

/ p>


  • 使用 DisposeOf 是强制性的,不管拥有或不拥有所有者的对象

  • 在析构函数中,或者在 DisposeOf 被调用后不久,引用不超出范围的情况,对象引用也应设置为 nil (详细解释在陷阱中)



可能有吸引力的是 DisposeOfAndNil 方法,但ARC使它更复杂比旧的 FreeAndNil 方法的情况,我建议使用简单的 DisposeOf - nil 序列来避免其他问题:

  Component.DisposeOf; 
组件:= nil;

尽管在许多情况下,即使没有遵循上述规则,代码也能正常运行,脆弱,容易被看似无关的地方引入的其他代码破坏。



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



DisposeOf 破坏ARC。它违反了ARC的黄金法则。任何对象引用可以是有效的对象引用,也可以是无限制,并引入第三个状态 - 处理的僵尸对象引用。



任何试图理解ARC内存管理的人都应该看看 DisposeOf ,除了只是解决Delphi特定的框架问题,而不是这个概念真的属于ARC本身。



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



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



除了免费通知机制,Delphi框架中还有其他类似的设计,适合手动内存管理,因为它们依赖于打破析构函数中的强参考循环,但这些设计不适用于ARC。



DisposeOf 方法可以直接调用对象析构函数,并启用这样的旧代码连同ARC。



这里必须注意一点。任何使用或继承自 TComponent 的代码自动成为适用ARC管理的上下文中的旧版代码,即使您今天写下。



从Allen Bauer的博客引用进入ARC侧


那么DisoseOf还能解决什么?在各种
Delphi框架(包括VCL和FireMonkey)之间,在活动的
通知或列表管理代码在一个类的构造函数和
析构函数中是很常见的。 TComponent的所有者/所有者模型是这样的设计的一个关键的
示例。在这种情况下,现有的组件
框架设计依赖于许多活动,而不是简单的资源
管理在析构函数中发生。



TComponent .Notification()是这样一个关键的例子。在这个
的情况下,正确的方式来配置一个组件,就是使用DisposeOf。 A
TComponent派生通常不是一个暂时的实例,而是
a更长寿命的对象,它也被一个
的整个系统所围绕,构成其他组件实例,例如表单,框架
和数据模块。在这种情况下,使用DisposeOf是适当的。




DisposeOf如何工作



要更好地了解当调用 DisposeOf 时会发生什么,需要知道Delphi对象销毁过程如何工作。



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


  1. 调用析构函数Destroy 方法链

  2. 清理对象管理字段 - 字符串,接口,动态数组(在ARC编译器下

  3. 从堆中释放对象内存

使用非ARC编译器释放对象



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



使用ARC编译器发布对象




  • Component.Free 组件:= nil - >减少对象引用计数之后是 a) b)




    • a) strong>如果对象引用计数为0 - >立即执行阶段 1 - > 2 - > 3

    • b)如果对象引用计数大于0没有其他事情



  • Component.DisposeOf - >立即执行阶段 1 ,当对象引用计数达到0时,阶段 2 3 将被执行。 DisposeOf 不减少调用引用的引用计数。




TComponent通知系统



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



在非ARC编译器下,该机制可确保不会导致指向无效释放对象的悬挂指针,而在ARC编译器下,清除对破坏组件的引用将减少其引用计数和中断强引用周期。



中触发免费通知 TComponent 析构函数,并且没有 DisposeOf 并直接执行析构函数,两个组件可以在整个应用程序生命周期内彼此保持强有力的引用。 p>

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



所以例如,如果你有 TEdit TPopupMenu 在您的表单上,并分配该弹出菜单以编辑 PopupMenu 属性,编辑将在其 FEditPopupMenu 中强制引用弹出菜单字段和弹出菜单将在其 FFreeNotifies 列表中强调编辑。如果要发布这两个组件中的任何一个,您必须在其上调用 DisposeOf ,否则它们将继续存在。



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



以下代码将基本上泄漏ARC下的两个组件,因为它们将保持强大的引用,并且在过程完成后,您将不再有任何外部引用这些组件之一。但是,如果您将 Menu.Free 替换为 Menu.DisposeOf ,您将触发免费通知机制并打破强大的参考周期。

  procedure ComponentLeak; 
var
编辑:TEdit;
菜单:TPopupMenu;
begin
编辑:= TEdit.Create(nil);
菜单:= TPopupMenu.Create(nil);

Edit.PopupMenu:=菜单; //创建强引用循环

Menu.Free; //菜单不会被释放,因为编辑保持强有力的引用
Edit.Free; //编辑不会被释放,因为菜单有很强的引用
end;



DisposeOf的陷阱



除了打破ARC这是坏的,因为当你打破它,你没有太多的使用,还有两个主要的问题,如何执行 DisposeOf 是开发人员应该意识到。



1。 DisposeOf 不减少调用引用的引用计数 QC report#126241

  type 
TFoo = class(TObject)
public
a:TObject;
结束

var
foo:TFoo;
b:TObject;

程序DoDispose;
var
n:integer;
begin
b:= TObject.Create;
foo:= TFoo.Create;
foo.a:= b;
foo.DisposeOf;
n:= b.RefCount; // foo还活着在这一点上,也保持b.RefCount为2而不是1
end;

procedure DoFree;
var
n:integer;
begin
b:= TObject.Create;
foo:= TFoo.Create;
foo.a:= b;
foo.Free;
n:= b.RefCount; // b.RefCount在这里是1,如预期的
结束;

2。 DisposeOf 不会清除实例内部托管类型引用 QC report#126243

  type 
TFoo = class(TObject )
public
s:string;
d:字节数组;
o:TObject;
结束

var
foo1,foo2:TFoo;

程序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 - 所有内部管理引用在这里仍然存在,
//并将直到foo2超出范围
end;

解决方法

 析构函数TFoo.Destroy; 
begin
s:='';
d:= nil;
o:= nil;
继承;
结束

上述问题的组合效果可以以不同的方式表现出来。从保存更多的分配的内存,而不是难以捕获由错误的,意外的引用计数的非所有对象和接口参考引起的错误。



由于 DisposeOf 不减少调用引用的引用计数对于$ code> nil 在析构函数中的引用非常重要,否则整个对象层次结构可以保持活着比所需要的更长,甚至在整个使用寿命期间也是如此。


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.

解决方案

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 QC report #126241

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 QC report #126243

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.

这篇关于如何在Android / iOS中释放组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆