Many of you will remember Delphi as revolutionizing Windows development when it was first released. Not only did Delphi deliver a complete application framework as a component library with visual design, but it did so with a true native language. This best of both worlds approach has kept Delphi a popular development solution on Windows.
With the most recent release of RAD Studio XE4, Delphi has become a multi-device and mobile solution that works for the first time on a non-Intel based processor, ARM. Delivering Delphi for iOS required changes to the language, compiler, runtime library, framework, and IDE. Perhaps the most important of these changes was the addition of automatic memory and object reclamation using reference counting.
Reclaiming Memory with ARC
From a language perspective, one of the new features we knew was going to be important on mobile was Automatic Reference Counting (ARC). ARC is a way to manage an object's lifetime without having to explicitly free/destroy the object. Basically, when the object goes out of scope, it is destroyed automatically (that is, when its reference count is 0). This is different from traditional garbage collection (GC) in that ARC is deterministic and objects are destroyed within the application flow rather than depending on a separate background task. ARC is important because it simplifies memory management considerably, which is critical on a mobile device with a low, fixed RAM profile. In fact, ARC is fully supported by Apple in the Objective-C language for iOS and is the preferred approach (as opposed to a GC) on Mac OS X as well.
As exciting and useful as enabling ARC for Delphi is, we also had to consider the huge amount of code that app developers would want to bring to mobile. In particular, a design goal of ours was to enable existing memory-management conventions and patterns to be ARC-compatible. Before ARC, Delphi developers used a specific coding pattern, based on the call to the
Free method and generally protected by a
try-finally exception-handling block. Given most Delphi code will be based on this pattern, it is important to understand what happens with
Free, even under ARC. In short, existing code will work.
For example, a typical Delphi application would have code like:
class procedure TMySimpleClass.TryFinally; var MyObj: TMySimpleClass; begin MyObj := TMySimpleClass.Create; try MyObj.DoSomething; finally MyObj.Free; end; end;
In the classic Delphi compiler,
Free is a method of
TObject, which checks whether the current reference is not
nil and, if it is, calls the
Destroy destructor, which removes the objects from memory after executing the proper destructor code.
In the new compiler, the call to
Free is replaced with the assignment of the variable to
nil. In the case where this was the last reference to the object, the object is removed from memory after calling its destructor. If there are other standing references when
Free is called, only the reference count is decremented.
Similarly, a call such as:
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 don't want to destroy objects used in another part of a program. There are scenarios, however, when you really want to execute the destructor code, such as closing a file handle or a database connection right away, regardless of the fact that there might be other pending references.
In other words, while they are often useless, calls to
FreeAndNil are generally completely harmless, and they can be kept in existing Delphi programs for the mobile platform. There are some limited scenarios, though, in which a different approach should be considered.
Forcing Object Harvesting
To allow the developer to force the execution of the destructor (without releasing the actual object from memory), the new compiler introduces a "dispose" pattern. If you call:
there is a forced execution of the destructor code, even if there are pending references. At this point, the object is placed in a special state so that the destructor won't be 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 significant in that you can query an object for it via the
Disposed property. This new method also is available on the classic compiler, but in this case, a call to
DisposeOf is remapped to a call to the
Free method. In other words, the new method makes no difference, but has been introduced to improve source code compatibility among different platforms.
Why is this dispose pattern required? Consider the case of a collection of elements or components owned by another component. A common usage pattern is to "destroy" a particular item in the collection in order to both clean up the item itself and remove it from the collection. Another common scenario is to destroy the collection or component owner and dispose of all the owned elements or components. In this case, if there are still pending references to the owned objects, it will likely force their destruction, or at least the execution of their destructor code.