【问题标题】:How to correctly use ARC with interfaces?如何正确使用带有接口的 ARC?
【发布时间】:2014-06-15 21:07:17
【问题描述】:

我无法理解带有接口的 ARC 究竟是如何工作的。在大多数教程中,我读过将接口设置为 nil 将调用 _Release,当 ARC 计数器达到零时,它将调用 Destroy。我已经阅读了一些教程,但我还不清楚。

type
  IXXX = interface(IInterface)
  end;

  TXXX = class(TInterfacedPersistent, IXXX)
  public
    constructor Create;
    destructor Destroy; override;
  end;


procedure TForm1.Button4Click(Sender: TObject);
var
  intf: IXXX;
begin
  intf := TXXX.Create; // IntfCopy  will be called. Message "create"
  intf := nil;         // IntfClear will be called. No message "destroy"
end;

{ TXXX }

constructor TXXX.Create;
begin
  inherited;

  showmessage('create');
end;

destructor TXXX.Destroy;
begin
  showmessage('destroy');

  inherited;
end;

当我运行这个程序时,我会收到一条消息“create”,但没有消息“destroy”。我查看了汇编代码,发现调用了System._IntfCopySystem._IntfClear

查看System._IntfClear 清楚地表明,当Source 为nil 时不会调用_Releaseintf := nil 也是如此。所以难怪为什么对象永远不会被销毁。

那么如何正确使用ARC呢?

A blog 建议不要接触接口,而只使用非引用计数版本。我知道如何覆盖 _AddRef_Release 以绕过/禁用 ARC,但我想使用 ARC,除非有充分的理由不使用它。

(使用 Delphi 6)

【问题讨论】:

  • 您说使用 Delphi 6 -- 我假设您的意思是 Delphi XE6,因为 Delphi 6 不支持 ARC,仅支持接口引用计数。
  • 我指的是 Delphi 6。对于“Interface ARC”,我指的是接口的 ARC。我知道普通对象在 Delphi 6 中没有 ARC/GC。

标签: delphi interface automatic-ref-counting


【解决方案1】:

您所派生的类TInterfacedPersistent 在其documentation 中有这样的说法:

TInterfacedPersistent 与所有持久对象一样,支持从流中读取和写入其属性的能力。此外,它还提供了 IInterface 方法(_AddRef、_Release 和 QueryInterface)的默认实现。此默认实现只是将这些调用传递给持久对象的所有者的接口(如果有)。

当引用计数降至零时,当然会调用_Release。但是没有任何反应,因为您的对象没有所有者。

如果您希望使用引用计数来管理生命周期,那么您应该从TInterfacedObject 派生。或者提供_AddRef_Release 的实现,它们对引用计数并在引用计数降至零时销毁对象。使用TInterfacedObject 作为如何实现它的模板。

对于它的价值,您链接到的文章包含非常糟糕的建议。不要理会它。如果要使用接口,请让引用计数管理生命周期,并仅通过接口引用对象。

【讨论】:

  • 感谢您指出实际问题。我注意到我上面的例子适用于TInterfacedObject,所以我一直在错误的地方寻找内存泄漏。我在我的项目中使用TInterfacedPersistent,因为我希望能够使用Assign 复制对象的内容(所以我覆盖AssignTo)。现在我发现我还需要覆盖 GetOwner 才能让 ARC 正常工作。但是当我没有父所有者结构时我该怎么办 - 只是一个我想要复制的持久对象? GetOwner 是否应该总是返回 Self?
  • 但是你将打破 ARC 的“全有或全无”规则。要么一直是ARC,要么根本不是。要使用 ARC,您需要避免通过接口以外的任何引用。
  • 嗯,好的。我想我误解了关于 TPersistent 的全部内容......我确实理解 TPersistent 是如何处理诸如表单之类的事情的。按钮将表单作为所有者,表单将应用程序作为所有者。然后一切都被序列化为流。现在我明白了,给定FChild := TXXX.Create(SomeOwner),在FChild 达到ARC=0 之后,SomeOwner._Release 被调用,并且一旦SomeOwner 的ARC 达到零,它将被销毁,IT 将释放FChild在它的析构函数中。所以,如果我想使用TPersistent,我将不得不始终使用所有者原则。
【解决方案2】:

我认为您可能想使用TInterfacedObject,但改用TInterfacedPersistent,它不会自动管理引用计数。

通常当我释放一个接口对象时,我总是会在前一行放置一个断言,以确保该对象实际上是一个接口对象。

procedure TForm1.Button4Click(Sender: TObject);
var
  intf: IXXX;
begin
  // create an interfaced object
  intf := TXXX.Create;

  // free an interfaced object
  if Assigned(intf) then
  begin
    assert(intf.InheritsFrom(TInterfacedObject));
    intf := nil;
  end;
end;

请注意,TSingletonImplementation 是另一个不自动管理引用计数的类。

【讨论】:

    【解决方案3】:

    关于您发布的博客链接,在某些情况下是合理的,尤其是当组件或控件归另一个组件所有时。

    所以你说如果有充分的理由不使用它。有一个:

    “如果你的对象是一个组件或一个控件由另一个组件拥有,那么它是基于 TComponent 的不同内存管理系统的一部分。虽然有些类混合了 TComponent 和接口引用的对象生命周期管理方法算起来,这很难正确实现”

    Not Using Reference Counting, the offical document said

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-12
      • 1970-01-01
      • 1970-01-01
      • 2018-08-01
      • 2014-06-26
      • 1970-01-01
      • 2016-08-30
      • 1970-01-01
      相关资源
      最近更新 更多