【问题标题】:Delphi ARC under Firemonkey for WindowsFiremonkey for Windows 下的 Delphi ARC
【发布时间】:2017-11-06 12:47:56
【问题描述】:

我正在观看 this 视频,其中 Marco 正在谈论自动引用计数。我已经知道在 Android 和 iOS (Firemonkey) 下我的对象是 ref 计数的,所以我不需要 try finally 块。

引用计数的实现是根据平台(VLC 或 FMX)还是操作系统?

我的意思是:

var a: TObject;
begin
 a := TObject.Create;
 a.use1;
 a.use2;
end;

如果它在 Android/iOS/Mac 上运行,这在 Firemonkey 中很好,我没有内存泄漏。但是如果我在 Windows 下运行它(并且我使用过 Firemonkey),我是否仍然会因为没有引用计数而导致内存泄漏?

无论如何,我从视频中了解到,try finallyFree 的调用,即使在 ARC 下,也不是不好的用法,但它只是没用。

【问题讨论】:

  • ... it runs on android/ios/mac, I have no memory leak. Mac/OSX 没有 ARC,会泄漏。
  • 我建议你总是使用 try-finally,因为如果你有机会在 Windows 上运行你的 android 应用程序,你就不会有内存管理问题
  • 不是“a.Create;”...应该是“a := TObject.Create;”

标签: delphi automatic-ref-counting


【解决方案1】:

ARC 是按操作系统平台实现的,而不是 GUI 框架。

Android、iOS 和 Linux 编译器对对象使用 ARC 内存管理。 Windows 和 OSX 编译器使用经典的手动内存管理,其中 ARC 仅支持接口引用,而不支持对象。

VCL 是一个仅限 Windows 的框架,它只能在经典编译器下运行。

另一方面,FMX 作为一个跨平台框架,并且使用不同的内存管理系统,这取决于它运行在哪个操作系统平台上。


try...finally Free 块在 ARC 编译器上确实没用(在安全保护对象释放的上下文中结合Free 方法)。 但是,如果您编写的跨平台代码必须在两种内存管理系统下都可以工作,则必须使用try...finallyFree

另一方面,如果您编写的代码只能在 ARC 下工作,您可以放心地省略 try...finallyFree

但是,在 ARC 上,您可能希望使用 Free(在 ARC 编译器上,它转换为 nil 赋值)或直接将 nil 分配给对象/接口引用,如果您需要在某个点释放对象,在它之前引用将超出范围。


上述规则有一个重要的例外 - TComponent 后代(包括 Firemonkey GUI 组件和控件),如果您在代码中创建和释放这些实例,则必须将 try...finally Free 块替换为 try...finally DisposeOf

您可以在How to free a component in Android / iOS阅读更多内容

这里要注意的重要一点:DisposeOf 有非常特定的用途,它不是打破引用循环和在 ARC 下释放对象的通用解决方案。在所有地方盲目使用它会导致内存泄漏。更详细的答案可以在上面的问答中找到 DisposeOf 的陷阱

【讨论】:

  • 好的,谢谢。是的,我使用 try .. finally 总是因为如果我需要一个程序在不同的平台上工作,我不会有泄漏(无论操作系统)。
  • "try...finallyFree 在 ARC 编译器上确实没用" - 这是一种误导。它们在 ARC 系统上是可选的,但它们并非无用。通过手动Freeing 一个对象,您可以控制 when 它的引用计数减少,这会影响 when 对象被释放。如果您需要尽快减少对象的引用计数而不是延迟减少,您不必必须等待对象超出范围。
  • @DalijaPrasnikar:编译器会将Free 翻译成nil。就像你说的,对于跨平台编码,你必须使用Free。但是try..finally 在 ARC 中并不是没用的。您过于关注它的一种用法 - 创建一个对象然后释放它。 try..finally不仅限于那个用法,别说没用。
  • @RudyVelthuis 是的,但在与Free 一起使用的内存管理上下文中,问题是关于try...finally。我在这里没有看到太多歧义。我认为很明显我不是在谈论try...finally
  • @RudyVelthuis 我更新了答案。我希望现在很清楚。
【解决方案2】:

引用计数 ARC 在 delphi 下非常令人困惑,它使开发变得更加困难,因为它设计得很糟糕(在 delphi 下)。

首先它是在 RTL 中构建的,它与 String 非常相似。它适用于平台(firemonkey)和操作系统(仅适用于 android/ios/unix)。因此,如果您想构建还针对窗口的多平台代码(很可能,至少只是为了调试您的代码)无论如何您都需要继续尝试 ... finally .. end;(是的我说你,非常糟糕的设计)。所以忘记你没有 .free 的例子,除非你想在移动设备下调试(祝你好运,每次编译 3 分钟,这根本不可能)

注意:为了让您的代码尽可能地兼容,一个很好的获取规则是将所有 .free 替换为 .disposeOF 后跟 nil,因为在移动设备下, free 是一个无操作操作,您的对象不会被破坏,并且可以在非常意外的时间被破坏(或者在最坏的情况下永远不会被破坏,如果您使用例如 TTask 很常见)。这些场景并不少见,特别是如果您经常使用在后台捕获到您的对象的匿名过程(对过程的引用)。

请始终牢记,循环 ref 很容易满足,而且相当 很难发现

.

你也必须知道(你没有问,但我扩展了一点答案)它们是delphi Tobject,java object,IOS object C object和Interface。所有人都有自己的规则,让每个人都感到困惑,最后没有人真正知道它是如何工作的(是的,ARC 糟糕设计的一部分也是它给人的困惑),甚至 emb 开发人员在他们的 delphi 源代码中也会出错,寻找例如这个问题:delphi + ios: release / retain and reference counting with objective-c object 这很简单,但没有任何答案

ARC 和 Objective-C 封装的对象

Delphi NextGen 编译器为所有 Delphi 对象实现自动引用计数 (ARC)。编译器将为您管理 TObject 的 __ObjAddRef 和 __ObjRelease 的逻辑。 Objective-C 代码使用相同的逻辑来调用保留和释放。不幸的是,由导入包装类和上面讨论的接口表示的 Objective-C 对象没有 ARC。在处理 Objective-C 对象时,您必须在正确的位置调用 retain 并释放自己。

ARC 和 JAVA 对象

在纸上它必须有效,但我个人不相信它,例如,如果你在循环中这样做:

for i := 0 to 100000 do begin
  aJstring := StringToJstring('a text'); 
  aStr := JstringToString(aJstring);
end;

通常这必须在正常世界中毫无问题地运行,但在 delphi 下,它会崩溃:(但无论如何在这里你没有 .release 所以你没有选择(除了将变量分配给 nil)。但是,当您选择为什么我建议始终使用 .disposeOF 后跟 nil 时,您可能会赢得数天/数周/数月的开发,从而避免一些讨厌的错误。

注意:当我想销毁一个对象时,我会调用这个函数:

{******************************}
Procedure ALFreeAndNil(var Obj);
var Temp: TObject;
begin
  Temp := TObject(Obj);
  if temp = nil then exit;
  TObject(Obj) := nil;

    {$IF defined(AUTOREFCOUNT)}
    if AtomicCmpExchange(temp.refcount{Target}, 0{NewValue}, 0{Compareand}) = 1 then begin // it's seam it's not an atomic operation (http://stackoverflow.com/questions/39987850/is-reading-writing-an-integer-4-bytes-atomic-on-ios-android-like-on-win32-win6)
      temp.Free;
      temp := nil;
    end
    else begin
      Temp.DisposeOf; // 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.
                      //
                      // 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.
      {$IF defined(DEBUG)}          
        if (Temp.RefCount - 1) and (not $40000000{Temp.objDisposedFlag}) <> 0 then
          ALLog('ALFreeAndNil', Temp.ClassName + ' | Refcount is not null (' + Inttostr((Temp.RefCount - 1) and (not $40000000{Temp.objDisposedFlag})) + ')', TalLogType.warn);
      {$IFEND}
      temp := nil;
    end;
    {$ELSE}
    temp.Free;
    temp := nil;
    {$IFEND}

end; 

这样,如果调用ALFreeAndNil后refcount不为0,那么它会在日志中产生警告(正在调试中),您可以调查

【讨论】:

  • 到处使用DisposeOf 是非常糟糕的建议。 DisposeOf 有几个缺陷,在不需要的地方使用它会带来另一组问题。
  • 你能告诉我其中一个缺陷吗?
  • 阅读我的answer under Pitfalls of DisposeOf 中提到的 QP 报告另外,DisposeOf 本身并不会清除所有参考周期。所以你可能认为你已经发布了所有东西,但实际上你没有。
  • 此外,关于DisposeOf 的不好建议,您的回答还提到了很多与使用try...finally Free 块的上下文中的对象内存管理无关的事情。匿名方法问题与变量捕获有关(与问题无关),并且包装 Objective-C 和 Java 对象也属于完全不同的类别。 Java 字符串泄漏的 AFAIK 问题在东京得到解决。
  • @DalijaPrasnikar 关于您在 DisposeOf 陷阱下的回答中提到的样本,是的,您必须始终执行 DisposeOf + nil(类似于 DisposeOfAndNil),但无论如何,对于这两个关于 disposeOF 的陷阱,我可以给您其他 100 个关于.FREE。同样是的, disposeOF 不会清除引用计数,但最好在内存中只保留指针大小使用的几个字节,而不是活动对象使用的内存;)
猜你喜欢
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 2011-12-31
  • 2017-08-05
  • 2016-12-11
相关资源
最近更新 更多