【问题标题】:What happens to Database connection objects(Mydac TMyConnection) under ARCARC下的数据库连接对象(Mydac TMyConnection)会发生什么
【发布时间】:2015-11-07 17:46:21
【问题描述】:

我研究过ARC下的内存管理 但我仍然不确定在这种情况下会发生什么

function foo() : boolean
var
    Mycon : TMyConnection
    MyQuery : TMyQuery
begin
    Mycon := TMyConnection.Create(nil);
    Mycon.ConnectString := MyConnection1.ConnectString;
    Mycon.ConnectionTimeout:= 3;
    MyQuery := TMyQuery.Create(nil);
    MyQuery.Connection := Mycon;
    Mycon.Connect;

    //Do a few Queries
end;

现在传统上我会调用 Free 来释放它们,但我知道 ARC 使用引用计数来释放对象, 一个对象一旦超出范围就会被释放,在这种情况下是在它被释放的查询之后。

现在我的问题是:TMyConnection 的活动连接会将对象保持在范围内吗?

我知道我总是可以将 Mycon 分配给 NIL 或调用 DisposeOf 来中断任何引用。

【问题讨论】:

    标签: delphi automatic-ref-counting


    【解决方案1】:

    我知道 ARC 使用引用计数来释放对象,并且对象一旦超出范围就会被释放

    更准确地说,当它的引用计数下降到 0 时它会被释放。差别很大,因为如果还有其他对它的活动引用,变量可能会超出范围而对象本身不会被释放。

    TMyConnection 的活动连接会将对象保持在范围内吗?

    这取决于几个因素:

    1. TMyQuery.Connection 属性是使用 strong 还是 weak 引用 TMyConnection 对象(即,支持其 @ 的 TMyQuery 字段是否987654325@属性是否有[weak]属性)。

    2. TMyQuery.Connection 属性设置器是否在 TMyConnection 对象上调用 FreeNotification()

    让我们看看最好的情况——引用并且没有FreeNotification()

    type
      TMyConnection = class(...)
        //...
      end;
    
      TMyQuery = class(...)
      private
        [weak] FConn: TMyConnection;
      published
        property Connection: TMyConnection read FConn write FConn;
      end;
    

    function foo() : boolean
    var
      Mycon : TMyConnection
      MyQuery : TMyQuery
    begin
      Mycon := TMyConnection.Create(nil); // <-- MyCon.RefCnt is now 1
      Mycon.ConnectString := MyConnection1.ConnectString;
      Mycon.ConnectionTimeout:= 3;
      MyQuery := TMyQuery.Create(nil); // <-- MyQuery.RefCnt is now 1
      MyQuery.Connection := Mycon; // <-- MyCon.RefCnt remains 1
      Mycon.Connect;
    
      *Do a few Queries*
    
    end; // <-- MyCon.RefCnt drops to 0, MyQuery.RefCnt drops to 0, OK!
    

    在这种情况下,由于TMyQuery 对象对TMyConnection 对象的引用,因此TMyConnection 的引用计数不会增加。因此,当MyConMyQuery 变量超出范围时,两个对象的引用计数都降为0,并且两个对象都被释放。

    现在让我们看看更糟糕的情况 - strong 引用和FreeNotification()。如果您将组件从 ARC 之前的版本迁移到基于 ARC 的系统而不重写它们来处理 ARC,您可能会遇到这种情况:

    type
      TMyConnection = class(...)
        //...
      end;
    
      TMyQuery = class(...)
      private
        FConn: TMyConnection;
        procedure SetConnection(AValue: TMyConnection);
      protected
        procedure Notification(AComponent: TComponent; Operation: TOperation); override;
      published
        property Connection: TMyConnection read FConn write SetConnection;
      end;
    
    procedure TMyQuery.Notification(AComponent: TComponent; Operation: TOperation);
    begin
      inherited;
      if (Operation = opRemove) and (AComponent = FConn) then
        FConn := nil;
    end;
    
    procedure TMyQuery.SetConnection(AValue: TMyConnection);
    begin
      if FConn <> AValue then
      begin
        if FConn <> nil then FConn.RemoveFreeNotification(Self);
        FConn := AValue;
        if FConn <> nil then FConn.FreeNotification(Self);
      end;
    end;
    

    function foo() : boolean
    var
      Mycon : TMyConnection
      MyQuery : TMyQuery
    begin
      Mycon := TMyConnection.Create(nil); // <-- MyCon.RefCnt is now 1
      Mycon.ConnectString := MyConnection1.ConnectString;
      Mycon.ConnectionTimeout:= 3;
      MyQuery := TMyQuery.Create(nil); // <-- MyQuery.RefCnt is now 1
      MyQuery.Connection := Mycon; // <-- MyCon.RefCnt is now 3, MyQuery.RefCnt is now 2
      Mycon.Connect;
    
      *Do a few Queries*
    
    end; // <-- MyCon.RefCnt drops to 2, MyQuery.RefCnt drops to 1, LEAKS!
    

    在这种情况下,由于 TMyQuery 对象有 2 个 strongTMyConnection 对象的引用(1 个用于支持 Connection 属性的字段,1 个在其 FreeNotification() 列表中),并且TMyConnection 有一个对TMyQuerystrong 引用(在其FreeNotification() 列表中),当MyCon 和@987654349 时,两个对象引用计数都会递增并且不会降至0 @ 变量超出范围,因此两个对象都被泄露了。

    为什么FreeNotification() 列表有strong 引用?因为在 XE3 中,Embarcadero 将 TComponent.FFreeNotifies 成员从 TList 更改为 TList&lt;TComponent&gt;。列表中的指针现在是类型化的,当T 派生自TObject 时,无法将TList&lt;T&gt; 标记为持有弱指针,因此它们使用强引用。这是 Embarcadero 尚未解决的已知问题,因为 XE8 仍在使用 TList&lt;TComponent&gt; 而不是回到 TList 或至少切换到 TList&lt;Pointer&gt;

    有关详细信息,请参阅此问题的答案:

    How to free a component in Android / iOS

    我知道我总是可以将 Mycon 分配给 NIL 或调用 DisposeOf 来中断任何引用。

    MyCon 设置为零只会释放该特定引用,但不会对其他引用产生任何影响,例如TMyQuery.Connection。另一方面,调用 MyCon.DisposeOf() 将释放对象并将所有 strong 引用保持在非零 Disposed 状态。

    但是,在这种情况下,您应该能够只清除 MyQuery.Connection 属性,使其有机会释放它可能对 TMyConnection 对象具有的任何 strong 引用。这适用于上述两种情况:

    // weak referencing, no FreeNotifcation():
    
    function foo() : boolean
    var
      Mycon : TMyConnection
      MyQuery : TMyQuery
    begin
      Mycon := TMyConnection.Create(nil); // <-- MyCon.RefCnt is now 1
      Mycon.ConnectString := MyConnection1.ConnectString;
      Mycon.ConnectionTimeout:= 3;
      MyQuery := TMyQuery.Create(nil); // <-- MyQuery.RefCnt is now 1
      MyQuery.Connection := Mycon; // <-- MyCon.RefCnt remains 1, MyQuery.RefCnt remains 1
      try
        Mycon.Connect;
        *Do a few Queries*
      finally
        MyQuery.Connection := nil; // <-- MyCon.RefCnt remains 1, MyQuery.RefCnt remains 1
      end;
    end; // <-- MyCon.RefCnt drops to 0, MyQuery.RefCnt drops to 0, OK!
    

    // strong reference, FreeNotification():
    
    function foo() : boolean
    var
      Mycon : TMyConnection
      MyQuery : TMyQuery
    begin
      Mycon := TMyConnection.Create(nil); // <-- MyCon.RefCnt is now 1
      Mycon.ConnectString := MyConnection1.ConnectString;
      Mycon.ConnectionTimeout:= 3;
      MyQuery := TMyQuery.Create(nil); // <-- MyQuery.RefCnt is now 1
      MyQuery.Connection := Mycon; // <-- MyCon.RefCnt is now 3, MyQuery.RefCnt is now 2
      try
        Mycon.Connect;
        *Do a few Queries*
      finally
        MyQuery.Connection := nil; // <-- MyCon.RefCnt drops to 1, MyQuery.RefCnt drops to 1
      end;
    end; // <-- MyCon.RefCnt drops to 0, MyQuery.RefCnt drops to 0, OK!
    

    【讨论】:

    • 值得一提的是,两个局部变量的终结顺序是不确定的吗?
    • 自动管理的变量以它们声明的相反顺序完成。这不是未定义的,它实际上是明确定义的。所有自动管理的类型(字符串、动态数组、接口、变体以及现在的 ARC 对象)都以这种方式工作。 Allen Bauer 已经记录在案,并且之前已经发布了有关实施的技术细节。自己尝试一下,您会看到编译器创建的代码会按照声明的相反顺序清理变量。
    • 我从未见过有记录的。你有参考吗?我不喜欢依赖实现细节。
    • 感谢您的详细回答,我假设使用强引用的 Firemonkey 移动平台我也应该为 SQLLite 的 FDQueries 的连接属性设置 NIL,否则它们也会被泄露?因为查询的connection属性也会保持FDConnection的ref count在0以上
    • @Peter-JohnJansen:如果持有TFDConnection 引用的底层TFDQuery 成员未标记为[weak],则可以。
    猜你喜欢
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    • 2012-12-19
    • 1970-01-01
    • 2017-04-20
    • 2011-12-08
    • 1970-01-01
    相关资源
    最近更新 更多