Automatic Reference Counting in Delphi Mobile Compilers
Go Up to Delphi Considerations for Multi-Device Applications
Delphi users have long been familiar with the notion of Automatic Reference Counting (ARC). In the past, the Delphi desktop compilers (DCC32, DCC64, DCCOSX) have supported ARC for interfaces (introduced in Delphi 3), dynamic arrays, and strings (AnsiString was introduced in Delphi 2). Now, the Delphi mobile compilers introduce automatic reference counting for classes. Accordingly, the notion of weak reference is also being introduced in order to manage "cycles", along with another feature: Operator Overloading for Classes.
For an in-depth detailed description of Automatic Reference Counting, see Release Notes for ARC (Apple Computers)
Contents
Automatic Reference Counting
Automatic Reference Counting is a way of managing the lifetime of an object without needing to free the object when it is no longer needed. A good example of this is a local variable that references an object that goes out of scope. Following this, the object is automatically destroyed. As seen above, Delphi already has reference counting support for strings and objects referenced through interface-type variables. However, ARC in the Delphi mobile compilers has some flexibility in order to solve issues like circular references that are hard to handle with interface-type variables.
Another notion introduced in the new mobile Delphi compilers is the weak reference. This solves some circular references issues.
The memory management scheme is the same for apps compiled for the iOS devices and iOS simulator:
- DCCIOSARM (the Delphi compiler for the 32-bit iOS Device) has Automatic Reference Counting by default.
- DCCIOSARM64 (the Delphi compiler for the 64-bit iOS Device) has Automatic Reference Counting by default.
- DCCIOS32 (the Delphi compiler for the iOS Simulator) also enables ARC, even though DCCIOS32 is based on the traditional compiler architecture.
Note: The only area where these new features surface in the RAD Studio source code is inside
{$AUTOREFCOUNT}
conditional blocks within the RTL (for example, in System.pas).
Coding style changes
Because the new mobile compilers support automatic reference counting, the code can be simplified significantly when you need to refer to temporary objects within a method. Memory management is ignored completely:
class procedure TMySimpleClass.CreateOnly;
var
MyObj: TMySimpleClass;
begin
MyObj := TMySimpleClass.Create;
MyObj.DoSomething;
end;
In the example seen above, the destructor for object is called as the program reaches the end statement, that is when the MyObj
variable goes out of scope.
There is also the possibility to stop using the object before the end of the method. You can do this by setting the variable to nil:
class procedure TMySimpleClass.SetNil;
var
MyObj: TMySimpleClass;
begin
MyObj := TMySimpleClass.Create;
MyObj.DoSomething (False); // True => raise
MyObj := nil;
// do something else
end;
In this case, the object is destroyed before the end of the method, exactly as we set the variable to nil. In case the DoSomething
procedure raises an exception, the nil assignment statement will be skipped. Ultimately, as the method terminates, the object is still destroyed.
As the lifetime of an object follows the program flow, you can query the reference count of an object (only on platforms with reference counting) using the new RefCount property:
public
property RefCount: Integer read FRefCount;
Note: Querying the reference count of an object is not recommended and, in general, should not be used.
The Speed of Interlocked Operations
The increment and decrement operations of the reference count of an object are accomplished using thread-safe operations so that the reference count instance member is thread-safe. This means that the reference count instance variable is properly memory fenced to ensure that all threads observe the change immediately and cannot modify a stale value.
Note: The Automatic Reference Counting mechanism does not protect from race conditions and deadlocks.
ARC and the {$AUTOREFCOUNT} Directive
You might consider using the {$IFDEF AUTOREFCOUNT}
directive in order to have the best code for both scenarios: ARC and traditional. AUTOREFCOUNT
defines code that uses automatic reference counting, such as code for the Delphi mobile compilers. This is an important directive, different from {$IFDEF NEXTGEN}
(the directive that defines the new language features of the mobile compilers). AUTOREFCOUNT
might be useful in the future, in case ARC is implemented on top of the Delphi desktop compilers as well.
The Free and DisposeOf methods under ARC
For classes, Delphi developers are accustomed to a different coding pattern, based on calling the Free
method, protected by a try-finally
block.
For example:
class procedure TMySimpleClass.TryFinally;
var
MyObj: TMySimpleClass;
begin
MyObj := TMySimpleClass.Create;
try
MyObj.DoSomething;
finally
MyObj.Free;
end;
end;
With the Delphi desktop compilers, Free
is a method of TObject
that checks if the current reference is not nil, and if this is the case, calls the Destroy
destructor, which removes the object from memory after executing the proper destructor code.
In the new Delphi mobile compiler, instead, the call to Free
is replaced with the assignment of the variable to nil. In case this was the last reference to the object, this is still removed from memory after calling its destructor. If there are other standing references, nothing happens (but a decrease in the reference count).
FreeAndNil (MyObj);
Similarly, a call like the one above sets the object to nil, and again triggers the object destruction only if there are no other variables referring to it. In most cases, this is correct, as you do not want to destroy objects used in another part of a program. On the other hand, there are scenarios when you need to execute the destructor code right away, regardless of the fact that there might be other pending references to the object. To allow developers to force the execution of the destructor (without releasing the actual object from the memory), the new compiler introduces a dispose pattern:
MyObject.DisposeOf;
Calling this forcefully executes the destructor code, even with existing pending references. At this point the object is placed in a special state, so that the destructor is not called again in case of further disposal operations or when the reference counting reaches zero and memory is actually released. This disposed state (or zombie state) is quite significant, and you can query an object for it using the Disposed
property.
As mentioned earlier, the classic try-finally blocks used with the Delphi desktop compilers still work fine under the new compiler, even if they are not required. However, in many cases, you might want to use the dispose pattern instead (unless you want to recompile the code for earlier versions of Delphi).
In the example below, the effect remains the same with the classic compiler, as DisposeOf
calls Free. Instead, on ARC, the code executes the destructor when expected (the same time as the classic compiler, but the memory is managed by the ARC mechanism.
var
MyObj: TMySimpleClass;
begin
MyObj := TMySimpleClass.Create;
try
MyObj.DoSomething;
finally
MyObj.DisposeOf;
end;
end;
Note: The storage for the Disposed flag is a bit in the
FRefCount
field, taking into account that an object has a limit of 230 references.
TObject Construction and Destruction Methods under ARC
This is a summary of the methods inside the TObject
class related to creation and destruction.
One way to look at the difference between Free
and DisposeOf
, is to consider the intent. When using Free
, the intent is that the user needs that particular reference detached from the instance. It does not imply any kind of disposal or deallocation. By contrast, DisposeOf
is the programmer's way of explicitly telling the instance that it needs to "clean itself up". It never necessarily implies deallocations, DisposeOf
does an explicit "pre-clean" of the instance, which then relies on the normal reference count semantics to eventually deallocate the instance.
Weak References
Another important concept for ARC is the role of weak references, which you can create by tagging them with [weak]
attribute. Suppose that two objects refer to each other using a field, and an external variable refers to the first. The reference count of the first object will be 2 (the external variable, and the second object), while the reference count of the second object is 1. Now, as the external variable goes out of scope, the two objects' reference count remains 1, and they remain in memory indefinitely.
To solve this type of situation, and many similar scenarios, is to use a weak reference to break the circular references when the last external reference goes out of scope.
A weak reference is a reference to an object that does not increase its reference count. To declare a weak reference, use the [weak]
attribute, supported by the Delphi mobile compilers.
Given the previous scenario, if the reference from the second object back to the first one is weak, as the external variable goes out of scope, both objects are destroyed. Here is an example of this situation:
type
TMyComplexClass = class;
TMySimpleClass = class
private
[Weak] FOwnedBy: TMyComplexClass;
public
constructor Create();
destructor Destroy (); override;
procedure DoSomething(bRaise: Boolean = False);
end;
TMyComplexClass = class
private
fSimple: TMySimpleClass;
public
constructor Create();
destructor Destroy (); override;
class procedure CreateOnly;
end;
In the following code snippet, the constructor of the complex class creates an object of the other class:
constructor TMyComplexClass.Create;
begin
inherited Create;
FSimple := TMySimpleClass.Create;
FSimple.FOwnedBy := self;
end;
Remember that the FOwnedBy
field is a weak reference, so it does not increase the reference count of the object it refers to, in this case the current object (self).
Given this class structure, we can write:
class procedure TMyComplexClass.CreateOnly;
var
MyComplex: TMyComplexClass;
begin
MyComplex := TMyComplexClass.Create;
MyComplex.fSimple.DoSomething;
end;
This causes no memory leak, given that the weak reference is properly used.
As a further example of the use of weak references, note this code snippet in the Delphi RTL, part of the TComponent class declaration:
type
TComponent = class(TPersistent, IInterface,
IInterfaceComponentReference)
private
[Weak] FOwner: TComponent;
If you use the weak attribute in code compiled by one of the desktop Delphi compilers, the attribute is ignored. Using DCC32, you have to make sure that you add the proper code in the destructor of an "owner" object to also free the "owned" object. Calling Free
is allowed, although the effect is different in the Delphi mobile compilers. The behavior is correct in both in most circumstances.
Note: When an instance has its memory released, all active [weak] references are set to nil. Just as a strong (normal) reference, a [weak] variable can only be nil or reference a valid instance. This is the main reason why a weak reference should be assigned to a strong reference before being tested for nil and dereferenced. Assigning to a strong reference does not allow the instance to be released prematurely.
var O: TComponent;// a strong reference begin O := FOwner; // assigning a weak reference to a strong reference if O <> nil then O.<method>;// safe to dereference end;
Note: You can only pass a [Weak] variable to a var or out parameter that is also marked as [Weak]. You cannot pass a regular strong reference to a [Weak] var or out parameter.
The Unsafe Attribute
To set the unsafe attribute on a function, use this syntax:
[Result: Unsafe] function ReturnUnsafe: TObject;
Warning: [Unsafe] can also be applied to variables (members) and parameters. It should be only used outside the System unit in very rare situations. It is considered dangerous and its use is not recommended as no code associated with reference counting is generated.
Note: You can only pass an [Unsafe] variable to a var or out parameter that is also marked as [Unsafe]. You cannot pass a regular strong reference to an [Unsafe] var or out parameter.
Operator Overloading for Classes
A very interesting side effect of using ARC memory management is that the compiler can handle the lifetime of temporary objects returned by functions. One specific case is that of temporary objects returned by operators. In fact, a brand new feature of the new Delphi compiler is the ability to define operators for classes, with the same syntax and model that has been available for records since Delphi 2006.
Note: The following code example works with the Delphi mobile (iOS) compilers, but cannot be compiled by the Delphi desktop compilers.
As an example, consider the following simple class:
type
TNumber = class
private
FValue: Integer;
procedure SetValue(const Value: Integer);
public
property Value: Integer read FValue write SetValue;
constructor Create(Value: Integer);
function ToString: string; override;
class operator Add (a, b: TNumber): TNumber;
class operator Implicit (n: TNumber): Integer;
class operator Implicit(n: Integer): TNumber;
end;
constructor TNumber.Create(Value: Integer); begin
inherited Create;
Self.Value := Value;
end;
class operator TNumber.Add(a, b: TNumber): TNumber; begin
Result := TNumber.Create(a.Value + b.Value); end;
class operator TNumber.Implicit (n: TNumber): Integer; begin
Result := n.Value;
end;
class operator TNumber.Implicit (n: Integer): TNumber; begin
Result := TNumber.Create(n);
end;
procedure TNumber.SetValue(const Value: Integer); begin
FValue := Value;
end;
function TNumber.ToString: string;
begin
Result := IntToStr(Value);
end;
procedure Test;
var
a, b, c: TNumber;
begin
a := 10;
b := 20;
c := a + b;
Writeln(c.ToString);
end;
See Also
- Automatic Reference Counting in C++
- iOS Mobile Application Development
- DCCIOS32, the Delphi Compiler for the iOS Simulator
- DCCIOSARM, the Delphi Compiler for the 32-bit iOS Device
- DCCIOSARM64, the Delphi Compiler for the 64-bit iOS Device
- Release Notes for ARC (Apple Computers)
- Using Reference Counting (about interfaces with Delphi desktop compilers)