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

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

问题描述

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

I dynamically create a TEdit on a form in Android:

edit := TEdit.Create(Self);

我想使用 edit.Free 释放它,但它仍然在形式上.

I want to free it using edit.Free, but it just still on form.

此代码在 win32 上运行良好,但在 Android 上失败.

This code works fine on win32, but failed on Android.

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

The same seems to happen not only for TEdit but for any component using Android or iOS.

推荐答案

10.4 更新

Delphi 10.4 Sydney 跨所有平台统一内存管理并删除了 ARC 编译器.换句话说,所有平台现在都遵循与 Windows 平台相同的内存管理规则.

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 在经典(非 ARC)编译器中

DisposeOf vs Free in classic (non ARC) compiler

  • DisposeOf 在经典编译器上调用 Free 并且在功能上表现相同
  • DisposeOf 仅用于向后兼容,在新代码(不必保持与 ARC 编译器的兼容性)中使用 Free 是首选
  • 现有代码中的DisposeOf没有改成Free
  • 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

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

There are two rules that should be followed when releasing any TComponent descendant object under Delphi ARC compilers (currently Android and iOS):

  • 使用 DisposeOf 是强制性的,无论对象是否拥有所有者
  • 在析构函数中或者在DisposeOf被调用后不久引用没有超出范围的情况下,对象引用也应该设置为nil(详细解释在Pitfalls中)
  • 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)

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

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 破坏 ARC.它违反了 ARC 的黄金法则任何对象引用都可以是有效的对象引用或为零,并引入了第三种状态 - 已处置的僵尸"对象引用.

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.

任何试图了解 ARC 内存管理的人都应该将 DisposeOf 视为仅解决 Delphi 特定框架问题的添加,而不是真正属于 ARC 本身的概念.

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.

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

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.

除了Free Notification机制外,Delphi框架中还有其他类似的设计适合手动内存管理,因为它们依赖于在析构函数中打破强引用循环,但这些设计不适用于ARC.

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 方法支持直接调用对象析构函数,并使此类遗留代码能够与 ARC 一起使用.

DisposeOf method enables direct calling of object destructor and enables such legacy code to play along with ARC.

这里必须注意一件事.任何使用或继承自 TComponent 的代码在适当的 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.

引自 Allen Bauer 的博客 向 ARC 方面屈服

Quote from Allen Bauer's blog Give in to the ARC side

那么 DisoseOf 还能解决什么问题?它在各种Delphi 框架(包括 VCL 和 FireMonkey),用于放置活动构造函数中的通知或列表管理代码和类的析构函数.TComponent 的 Owner/Owned 模型是关键这种设计的例子.在这种情况下,现有组件框架设计依赖于许多活动,而不是简单的资源管理"发生在析构函数中.

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() 是这种事情的一个关键例子.在这在这种情况下,处置"组件的正确方法是使用 DisposeOf.一个TComponent 导数通常不是瞬态实例,而是一个寿命更长的物体,它也被整个系统包围构成表单、框架等事物的其他组件实例和数据模块.在这种情况下,使用 DisposeOf 是合适的.

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.

DisposeOf 的工作原理

为了更好地理解调用DisposeOf时到底发生了什么,有必要了解Delphi对象销毁过程是如何工作的.

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.

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

There are three distinct stages involved in releasing object in both ARC and non-ARC Delphi compilers

  1. 调用destructor Destroy方法链
  2. 清理对象管理字段 - 字符串、接口、动态数组(在 ARC 编译器下也包括普通对象引用)
  3. 从堆中释放对象内存

使用非 ARC 编译器释放对象

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

使用 ARC 编译器释放对象

  • Component.FreeComponent := nil ->减少对象引用计数,后跟 a)b)

  • Component.Free or Component := nil -> decreases object reference count followed by a) or b)

  • a) 如果对象引用计数为 0 ->立即执行阶段 1 ->2 ->3
  • b) 如果对象引用计数大于 0,则不会发生其他事情

  • 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 ->立即执行阶段 1,阶段 23 将在对象引用计数达到 0 后执行. DisposeOf不会减少调用引用的引用计数.

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 通知系统

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

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.

在非 ARC 编译器下,该机制可确保您最终不会出现指向无效释放对象的悬空指针,而在 ARC 编译器下,清除对销毁组件的引用将减少其引用计数并破坏强引用循环.

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 机制在 TComponent 析构函数中被触发,没有 DisposeOf 和析构函数的直接执行,两个组件可以持有对每个组件的强引用其他在整个应用程序生命周期内保持自己的生命.

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.

FFreeNotifying 包含对通知感兴趣的组件列表的列表声明为 FFreeNotifying: TList,它将存储对任何注册组件的强引用.

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.

因此,例如,如果您的表单上有 TEditTPopupMenu 并将该弹出菜单分配给 edit 的 PopupMenu 属性,则编辑将保持强大在其 FEditPopupMenu 字段中引用弹出菜单,并且弹出菜单将在其 FFreeNotifying 列表中拥有对编辑的强引用.如果你想释放这两个组件中的任何一个,你必须对它们调用 DisposeOf 否则它们将继续存在.

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.

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

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;

DisposeOf 的陷阱

除了破坏 ARC 之外,这本身就是不好的,因为当您破坏它时,您并没有太多使用它,还有两个主要问题是开发人员应该如何实现 DisposeOf请注意.

1.DisposeOf 不会减少调用引用时的引用计数 QP 报告 RSP-14681

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 不清理实例内部托管类型引用 QP 报告RSP-14682

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;

解决方法

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.

由于 DisposeOf 不会减少调用引用的引用计数,因此在析构函数中 nil 此类引用很重要,否则整个对象层次结构可以比需要的时间长得多,并且在某些情况下甚至在整个应用程序生命周期内.

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 不能用于解析所有循环引用

3. DisposeOf cannot be used to resolve all circular references

DisposeOf 最后但并非最不重要的问题是,只有在析构函数中有解决循环引用的代码时,它才会破坏循环引用 - 就像 TComponent 通知系统那样.

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.

应该使用引用之一上的 [weak] 和/或 [unsafe] 属性来打破析构函数未处理的此类循环.这也是首选的 ARC 做法.

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 不应该被用作打破所有 引用循环(它从来没有设计过的)的快速修复,因为它不会工作,滥用它会导致难以跟踪内存泄漏.

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.

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

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;

以上代码会泄漏子对象和父对象实例.结合 DisposeOf 不清除内部托管类型(包括字符串)的事实,这些泄漏可能很大,具体取决于您存储在内部的数据类型.打破这种循环的唯一(正确)方法是更改​​ TChild 类声明:

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;

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

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