我知道 ARC 使用引用计数来释放对象,并且对象一旦超出范围就会被释放
更准确地说,当它的引用计数下降到 0 时它会被释放。差别很大,因为如果还有其他对它的活动引用,变量可能会超出范围而对象本身不会被释放。
TMyConnection 的活动连接会将对象保持在范围内吗?
这取决于几个因素:
TMyQuery.Connection 属性是使用 strong 还是 weak 引用 TMyConnection 对象(即,支持其 @ 的 TMyQuery 字段是否987654325@属性是否有[weak]属性)。
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 的引用计数不会增加。因此,当MyCon 和MyQuery 变量超出范围时,两个对象的引用计数都降为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 个 strong 对 TMyConnection 对象的引用(1 个用于支持 Connection 属性的字段,1 个在其 FreeNotification() 列表中),并且TMyConnection 有一个对TMyQuery 的strong 引用(在其FreeNotification() 列表中),当MyCon 和@987654349 时,两个对象引用计数都会递增并且不会降至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
我知道我总是可以将 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!