I know ARC uses reference counting to free objects, And a object is freed once it goes out of scope
More accurately, it is freed when its reference count falls to 0. Big difference, because a variable can go out of scope without the object itself being freed if there are still other active references to it.
Would the active connection of the TMyConnection keep the object in scope?
It depends on a couple of factors:
whether the TMyQuery.Connection
property uses strong or weak referencing to the TMyConnection
object (ie, does the TMyQuery
field that backs its Connection
property have the [weak]
attribute on it or not).
whether the TMyQuery.Connection
property setter calls FreeNotification()
on the TMyConnection
object.
Let's look at the best case scenario - weak referencing and no 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!
In this scenario, since the TMyQuery
object has a weak reference to the TMyConnection
object, the TMyConnection
's reference count is not incremented. Thus, both object reference counts fall to 0 when the MyCon
and MyQuery
variables go out of scope, and both objects are freed.
Now let's look at the worse case scenario - strong referencing and FreeNotification()
. This is what you might run into if you migrate the components from a pre-ARC version to an ARC-based system without re-writing them to handle 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!
In this scenario, since the TMyQuery
object has 2 strong references to the TMyConnection
object (1 for the field backing the Connection
property, and 1 in its FreeNotification()
list), and TMyConnection
has a strong reference to TMyQuery
(in its FreeNotification()
list), both object reference counts are incremented and do not fall to 0 when the MyCon
and MyQuery
variables go out of scope, and thus both objects are leaked.
Why do the FreeNotification()
lists have strong references? Because in XE3, Embarcadero changed the TComponent.FFreeNotifies
member from a TList
to a TList<TComponent>
. The pointers in the list are now typed, and there is no way to mark a TList<T>
as holding weak pointers when T
is derived from TObject
, so they are using strong referencing. This is a known problem that Embarcadero has not addressed yet, as XE8 is still using TList<TComponent>
instead of going back to TList
or at least switching to TList<Pointer>
.
See the answers to this question for more details:
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.
Setting MyCon
to nil would only release that particular reference, but would not have any effect on other references, like TMyQuery.Connection
. Calling MyCon.DisposeOf()
, on the other hand, would release the object and leave all strong references in a non-nil Disposed
state.
However, in this kind of situation, you should be able to just clear the MyQuery.Connection
property to give it a chance to release any strong references it may have to the TMyConnection
object. This works in both scenarios discribed above:
// 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!