ARC下的数据库连接对象(Mydac TMyConnection)会发生什么

时间:2015-08-14 12:51:36

标签: delphi automatic-ref-counting

我在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为Free,但我知道ARC使用引用计数来释放对象, 一个对象在超出范围时被释放,在这种情况下,它将在查询被释放之后被释放。

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

我知道我总是可以将Mycon分配给NIL或者调用DisposeOf来破坏任何参考。

1 个答案:

答案 0 :(得分:3)

  

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

就会被释放

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

  

TMyConnection的活动连接是否会将对象保留在范围内?

取决于有几个因素:

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

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

  3. 让我们看一下最佳案例情景 - 引用而不是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,并且两个对象都将被释放。

    现在让我们来看看更糟糕的情况 - 引用和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个TMyConnection对象的引用(1表示支持Connection属性的字段,1在FreeNotification()列表中),TMyConnectionTMyQueryFreeNotification()列表)中有引用,两个对象引用计数都会增加当MyConMyQuery变量超出范围时,不会降为0,因此两个对象都会泄露。

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

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

    How to free a component in Android / iOS

      

    我知道我总是可以将Mycon分配给NIL或者调用DisposeOf来破坏任何参考。

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

    但是,在这种情况下,您应该能够清除MyQuery.Connection属性,以便有机会释放它可能对{{1}提供的任何引用对象。这适用于上述两种情况:

    TMyConnection

    // 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!