Delphi Windows Firemonkey 下的 ARC

Delphi ARC under Firemonkey for Windows

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

引用计数实现是否根据平台(VLC 或 FMX)或 OS?

我的意思是:

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

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

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

ARC 是根据 OS 平台而不是 GUI 框架实现的。

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

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

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


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

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

然而,在 ARC 上你可能想使用 Free(在 ARC 编译器上它转换为 nil 赋值)或者直接将 nil 赋值给 object/interface 引用,如果你需要在某个点释放对象,以免它的引用超出范围。


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

您可以在 How to free a component in Android / iOS

阅读更多内容

这里需要注意的重要事项:DisposeOf 有非常特殊的用途,它不是打破引用循环和释放 ARC 下对象的通用解决方案。在所有地方盲目使用它会导致内存泄漏。可以在上面 Q/A DisposeOf 的陷阱

下找到更详尽的答案

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

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

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

Always keep in mind that circular ref are quite easy to meet and quite hard to detect

.

在你必须知道之后(你没有问,但我扩展了一点答案)他们是 delphi Tobject,java object,IOS objective C 对象和接口。所有这些都有自己的规则,让每个人都感到困惑,最后没有人真正知道它是如何工作的(是的,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 代码使用相同的逻辑来调用保留和释放。不幸的是,由导入包装器 class 和上面讨论的接口表示的 Objective-C 对象没有 ARC。在处理 Objective-C 对象时,你必须在正确的点调用 retain 和 release 自己。

ARC 和 JAVA 对象

在纸上它必须工作,但我个人不相信它,例如,如果你在循环中做:

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

通常这必须 运行 在正常世界中没有任何问题,但在 delphi 下它会崩溃 :( 但无论如何你没有 .release 所以你没有选择(除了将变量分配给 nil)。但是当你有选择时,为什么 我建议总是使用 .disposeOF 后跟 nil,你可能会赢得 days/weeks/month 的开发转义一些讨厌的错误。

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

{******************************}
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 (
      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 000000{Temp.objDisposedFlag}) <> 0 then
          ALLog('ALFreeAndNil', Temp.ClassName + ' | Refcount is not null (' + Inttostr((Temp.RefCount - 1) and (not 000000{Temp.objDisposedFlag})) + ')', TalLogType.warn);
      {$IFEND}
      temp := nil;
    end;
    {$ELSE}
    temp.Free;
    temp := nil;
    {$IFEND}

end; 

这样,如果在调用 ALFreeAndNil 后引用计数不为 0,则会在日志中生成警告(正在调试),您可以调查