我在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

    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;
    

    09-11 03:57