Can someone provide an explanation of a weak reference in Delphi?
I noticed that the concept is often mentioned in some library/framework source code I scrutini
By default in Delphi, all references are either:
pointer
and class
instances;integer, Int64, currency, double
or record
(and old deprecated object
or shortstring
);string, widestring, variant
or a dynamic array);interface
instances;The main issue with strong reference counting is the potential circular reference problem. This occurs when an interface
has a strong reference to another, but the target interface
has a strong pointer back to the original. Even when all other references are removed, they still will hold on to one another and will not be released. This can also happen indirectly, by a chain of objects that might have the last one in the chain referring back to an earlier object.
See the following interface definition for instance:
IParent = interface
procedure SetChild(const Value: IChild);
function GetChild: IChild;
function HasChild: boolean;
property Child: IChild read GetChild write SetChild;
end;
IChild = interface
procedure SetParent(const Value: IParent);
function GetParent: IParent;
property Parent: IParent read GetParent write SetParent;
end;
The following implementation will definitively leak memory:
procedure TParent.SetChild(const Value: IChild);
begin
FChild := Value;
end;
procedure TChild.SetParent(const Value: IParent);
begin
FParent := Value;
end;
In Delphi, most common kind of reference-copy variables (i.e. variant, dynamic array or string) solve this issue by implementing copy-on-write. Unfortunately, this pattern is not applicable to interface, which are not value objects, but reference objects, tied to an implementation class, which can't be copied.
Note that garbage collector based languages (like Java or C#) do not suffer from this problem, since the circular references are handled by their memory model: objects lifetime are maintained globally by the memory manager. Of course, it will increase memory use, slowdown the process due to additional actions during allocation and assignments (all objects and their references have to be maintained in internal lists), and may slow down the application when garbage collector enters in action.
One common solution with languages with no garbage-collection (like Delphi) is to use Weak pointers, by which the interface is assigned to a property without incrementing the reference count. In order to easily create a weak pointer, the following function could be used:
procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface);
begin
PPointer(aInterfaceField)^ := Pointer(aValue);
end;
Therefore, it could be used as such:
procedure TParent.SetChild(const Value: IChild);
begin
SetWeak(@FChild,Value);
end;
procedure TChild.SetParent(const Value: IParent);
begin
SetWeak(@FParent,Value);
end;
You can try to read my blog post about weak references in Delphi - and its associated source code: we have implemented direct weak reference, and "zeroing" weak reference interface handling from Delphi 6 up to XE2.
In fact, in some cases, you'll need to set the interface weak fields to nil
, if you release the reference instance before its child, to avoid any Access Violation issue. This is called "Zeroing Weak pointers", and what Apple implemented with the ARC model, and that we tried to implement in Delphi.