android - 如何在Android/iOS中释放组件

标签 android ios delphi components firemonkey

我在Android中的表单上动态创建TEdit:

edit := TEdit.Create(Self);

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

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

不仅对于TEdit,而且对于使用Android或iOS的任何组件,似乎都发生了同样的情况。

最佳答案

更新为10.4
Delphi 10.4 Sydney跨所有平台统一了内存管理,并删除了ARC编译器。换句话说,现在所有平台都遵循与Windows平台相同的内存管理规则。
经典(非ARC)编译器中的DisposeOfFree
经典编译器上的

  • DisposeOf调用Free并在功能上表现相同的
  • DisposeOf仅用于向后兼容,在首选使用Free的新代码(不必与ARC编译器保持兼容性)中,首选
  • 在现有代码DisposeOf中没有被更改为Free

  • 原始答案,对ARC编译器有效:
    简短答案
    在Delphi ARC编译器(当前为Android和iOS)下释放任何TComponent后代对象时,应遵循两个规则:

    强制使用DisposeOf
  • ,无论对象是否具有所有者
  • 析构函数中的
  • 或在调用DisposeOf后不久引用未超出范围的情况下,对象引用也应设置为nil(在陷阱中进行详细说明)

  • 拥有DisposeOfAndNil方法可能很有吸引力,但是ARC使它比旧的FreeAndNil方法复杂得多,我建议使用纯DisposeOf - nil序列来避免其他问题:
    Component.DisposeOf;
    Component := nil;
    
    尽管在许多情况下,即使不遵守上述规则,代码也可以正常运行,但此类代码非常脆弱,很容易被看似无关的地方引入的其他代码破坏。
    ARC内存管理中的DisposeOfDisposeOf中断ARC。它违反了ARC 的黄金法则。任何对象引用都可以是有效的对象引用,也可以是nil ,并且引入了第三种状态-放置了“僵尸” 对象引用。
    任何试图了解ARC内存管理的人都应该看DisposeOf,就像只是解决Delphi特定框架问题的加法,而不是真正属于ARC本身的概念。
    为什么DisposeOf在Delphi ARC编译器中存在?TComponent类(及其所有后代)在设计时考虑了手动内存管理。它使用与ARC内存管理不兼容的通知机制,因为它依赖于破坏析构函数中的强引用周期。由于TComponent是Delphi框架所依赖的基本类之一,因此它必须能够在ARC内存管理下正常运行。
    除了Free Notification机制之外,Delphi框架中还有其他类似的设计适用于手动内存管理,因为它们依赖于破坏析构函数中的强引用周期,但是这些设计不适合ARC。DisposeOf方法可以直接调用对象析构函数,并使这些旧代码可以与ARC一起播放。
    这里必须注意一件事。在今天的ARC管理下,即使您今天编写,使用或继承自TComponent的任何代码也会自动成为旧代码。
    引用艾伦·鲍尔(Allen Bauer)的博客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.


    DisposeOf如何工作
    为了更好地了解调用DisposeOf时到底发生了什么,有必要知道Delphi对象销毁过程是如何工作的。
    在ARC和非ARC Delphi编译器中释放对象涉及三个不同的阶段
  • 调用destructor Destroy方法链
  • 清理对象管理的字段-字符串,接口(interface),动态数组(也在包含普通对象引用的ARC编译器下)
  • 从堆中释放对象内存

  • 使用非ARC编译器发布对象Component.Free->立即执行阶段1 -> 2 -> 3 使用ARC编译器释放对象
  • Component.FreeComponent := nil->减少对象引用计数,后跟 a) b)
  • a)如果对象引用计数为0->立即执行阶段1 -> 2 -> 3
  • b)如果对象引用计数大于0,则无其他 react

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

  • TComponent通知系统TComponent Free Notification机制通知注册的组件特定的组件实例正在释放。被通知的组件可以在虚拟Notification方法中处理该通知,并确保清除了它们可能在销毁的组件上保留的所有引用。
    在非ARC编译器下,该机制可确保您不会导致指向无效对象(已释放对象)的悬空指针结束;在ARC编译器下,清除对销毁组件的引用将减少其引用计数并破坏强引用周期。
    Free Notification析构函数中触发了TComponent机制,并且没有DisposeOf和析构函数的直接执行,两个组件可以相互保持强引用,从而在整个应用程序生存期内保持自身的生命。
    包含对通知感兴趣的组件列表的FFreeNotifies列表被声明为FFreeNotifies: TList<TComponent>,它将存储对任何已注册组件的强引用。
    因此,例如,如果您的表单上有TEditTPopupMenu并将该弹出菜单分配给edit的PopupMenu属性,则edit将在其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;
    
    DisposeOf的陷阱
    除了破坏ARC之外,它本身也是不好的,因为当破坏ARC时,它并没有太多使用,开发人员还应注意DisposeOf的实现方式,这还有两个主要问题。
    1. DisposeOf不会在调用引用 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 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;
    
    上述问题的综合影响可能以不同的方式体现出来。从保留不必要的分配内存到难以捕获由所包含的非拥有对象和接口(interface)引用的错误,意外引用计数引起的错误。
    由于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;
    
    上面的代码将泄漏子对象实例和父对象实例。结合DisposeOf无法清除内部托管类型(包括字符串)的事实,这些泄漏可能会很大,具体取决于您存储在内部的数据类型。打破这种循环的唯一(正确)方法是更改​​TChild类声明:
      TChild = class(TObject)
      public
        [weak] var Parent: TParent;
        constructor Create(AParent: TParent);
      end;
    

    关于android - 如何在Android/iOS中释放组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27818697/

    相关文章:

    安卓屏幕方向 : Portrait doesn't work

    android - 屏幕分辨率

    ios - 从详细 View 返回时,UITableView 与状态栏和导航栏重叠

    delphi - 从序列化数据创建数字人物指纹模板

    arrays - 如何在Delphi中使用变体数组

    android - OpenGL ES 2.0 无法在 Android 4.0.3 上呈现

    java - 使用标准格式的时间值启动 AlarmManager 的最简单方法(ex 7 :30)

    ios - 为什么当我认为我没有改变任何东西时,我突然在 Xcode 中收到随机错误?

    ios - uitableviewcell 内的 UIButton 触发单元格而不是按钮

    delphi - 使用delphi检测USB驱动器/设备