1

I have done research on memory management under ARC but I am still not sure what would happen in this situation

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;

Now traditionally speaking I would call Free to Free them but I know ARC uses reference counting to free objects, And a object is freed once it goes out of scope, which in this case would be after the queries it is freed.

Now my question is : Would the active connection of the TMyConnection keep the object in scope?

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

Peter-John Jansen
  • 603
  • 1
  • 7
  • 26

1 Answers1

3

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:

  1. 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).

  2. 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!
Community
  • 1
  • 1
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Is it worth mentioning that the finalization order of two local variables is undefined? – David Heffernan Aug 14 '15 at 20:52
  • Auto-managed variables are finalized in the reverse order that they are declared. This is not undefined, it is actually well-defined. All auto-managed types (strings, dynamic arrays, interfaces, variants, and now ARC objects) work this way. Allen Bauer has gone on record stating as much, and the technical details about the implementation have been posted before. Try it for yourself, you will see the compiler creates code that cleans up the variables in the reverse order of their declarations. – Remy Lebeau Aug 14 '15 at 21:05
  • I've never seen that documented. Do you have a reference? I don't like to rely on implementation details. – David Heffernan Aug 14 '15 at 21:20
  • Thanks for the detailed answer, I am assuming then with Firemonkey mobile platforms using strong references that I should also NIL the connection property for my FDQueries for SQLLite, otherwise they will be leaked aswell? Because the connection property of the query will also keep the ref count of the FDConnection above 0 – Peter-John Jansen Aug 20 '15 at 10:17
  • @Peter-JohnJansen: if the underlying `TFDQuery` member that holds the `TFDConnection` reference is not marked as `[weak]`, then yes. – Remy Lebeau Aug 20 '15 at 11:18