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

What happens to Database connection objects(Mydac TMyConnection) under ARC

我研究过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 来打破任何引用。

I know ARC uses reference counting to free objects, And a object is freed once it goes out of scope

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

Would the active connection of the TMyConnection keep the object in scope?

这取决于几个因素:

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

  2. TMyQuery.Connection属性setter是否在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 属性,并在其 FreeNotification() 列表中有 1 个),并且 TMyConnection 具有对 TMyQuerystrong 引用(在其 FreeNotification() 列表),当 MyConMyQuery 变量超出范围时,两个对象引用计数都会递增并且不会下降到 0,因此两个对象都会泄漏。

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

查看此问题的答案了解更多详情:

How to free a component in Android / iOS

I know I could always just assign Mycon to NIL or call DisposeOf to break any refrences.

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

然而,在这种情况下,您应该能够清除 MyQuery.Connection 属性 以使其有机会释放任何 strong它可能必须引用 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!

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