我在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来破坏任何参考。
答案 0 :(得分:3)
我知道ARC使用引用计数来释放对象,并且一旦对象超出范围
就会被释放
更确切地说,当引用计数降为0时,它被释放。差别很大,因为如果对象本身仍有其它活动引用,则变量可能超出范围而不释放对象本身。
TMyConnection的活动连接是否会将对象保留在范围内?
取决于有几个因素:
TMyQuery.Connection
属性是否使用 strong 或 weak 引用TMyConnection
对象(即,TMyQuery
支持其Connection
属性的字段是否包含[weak]
属性。)
TMyQuery.Connection
属性设置器是否在FreeNotification()
对象上调用TMyConnection
。
让我们看一下最佳案例情景 - 弱引用而不是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,并且两个对象都将被释放。
现在让我们来看看更糟糕的情况 - 强引用和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()
列表中),TMyConnection
对TMyQuery
(FreeNotification()
列表)中有强引用,两个对象引用计数都会增加当MyCon
和MyQuery
变量超出范围时,不会降为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!