This blog post delves into some technical information about ARC and the available types of references in the Delphi mobile compilers for iOS and Android. The reason for this blog post is that there has been very little technical information about this topic and I've personally only recently come to realize -- in conversations with Embarcadero developers, particularly Allen Bauer -- some of the implications (so that my Object Pascal Handbook, while correct, can be considered incomplete).
Here I'm not going to repeat all of in the information already in my book and other sources, but provide a short overview and focus on the latest part, unsafe references, which is the most neglected one so far.
What is ARC?
Automatic Reference Counting is a memory management model based on tracking the references to a single object in memory and disposing the object in a fairly deterministic way when all references go out of scope. Unlike garbage collection, ARC happens in the thread of execution and in synch with the program flow, even if the flow can be fairly complex to easily understand when each given object will get deleted. (Not that here I want to delve into the merits of ARC vs. GC, this could be a whole different blog post). ARC is the standard memory model for Apple programming languages on both OS X and iOS.
Delphi mobile compilers all embrace ARC as the primary model, with ways to mitigate the transition from the traditional memory model thanks to extra methods built into the TObject class. When all references to a Delphi object go out of scope, the object is deleted and its destructor called. You can set a reference to nil in advance, to make this happen sooner, and even force the execution of the destructor in advance (calling DisposeOf).
Again, a lot has been written about ARC and ARC in Delphi, and I don't really have too much to add here.
What is a Weak Reference?
Weak references are a fundamental element of the ARC model. With ARC, unlike a GC, there is no loop detection so you need to "break" any loop using a weak references. This is something I cover in my book, I wrote about in white papers, and that is documented in several other sources. The implication of weak references is that these are managed. The system tracks all pending weak references, and if an object is destroyed and there is a weak references pending, this is automatically set to nil. This way you can check if a weak reference is nil to verify the object the weak reference is pointing to is still in memory -- something not available with the traditional memory model. The management of weak references has a runtime cost (although this cost is lower in Delphi than in Apple's implementation), as the system need to store them and track them. A large number of weak references might become expensive at runtime.
Can we skip using weak references altogether and use a manual, traditional approach? This is certainly a possibility, as explained below, however if you need to set up another complex object destruction notification system of your own, you might end up with a system that implies writing more code and have a larger runtime penalty compared to weak references.
What is an Unsafe Reference?
The last element, and the main point of this entire blog post, is the existence of unsafe references. There are a few documented examples of their use (including in my book) mostly focused around the scenario of handling temporary objects. A classic example is that of a function creating and returning an instance. At the point the function returns and before the object is assigned to another variable (for example as the result of an expression) the instance might end up with exactly zero references, and could be subject to (unwanted) disposal.
However, unsafe references can be useful in other scenarios. You declare one with the unsafe keyword, or (even better) the [unsafe] attribute. You can use this attribute to decorate a local variable or a field in a class or record.
What is in essence an unsafe reference under ARC compilers? It behaves basically as a traditional non-reference-counted reference of the Delphi world. No increase or decrease of the reference count happens when an unsafe reference is assigned an object or goes out of scope. No check for zero references is performed. Just like with a "good old" Delphi reference. (Along the same line mixing unsafe and ARC references can get you in some tricky scenarios, more or less like mixing interface and object references on the Windows compilers).
Does this mean developers should scrap ARC and go for the traditional model? To me this does make sense in some very specific scenarios, in which another memory management model is already in action. In general, ARC is a very powerful and complete model I recommend understanding and embracing, rather than fighting. Make sure you learn your options, and than make an decision, rather than taking a shortcut to the scenario you know from the past.
Consider also that our entire library uses ARC and weak references (although some of those might end up moving to unsafe references in the future).
A Roundup Micro Demo
To round up the blog post, let me finish with a very simple code demo, to highlight in less than 10 lines of code the differences between regular ARC references, weak references, and unsafe references. Consider this code snippet:
var myObj: TMyClass; myObj2: TMyClass; [weak] myObjW: TMyClass; [unsafe] myObjU: TMyClass; begin myObj := TMyClass.Create; Show(myObj.RefCount.ToString); myObj2 := myObj; Show(myObj.RefCount.ToString); myObjW := myObj; Show(myObj.RefCount.ToString); myObjU := myObj; Show(myObj.RefCount.ToString); myObj2 := nil; myObj := nil; // frees the object // are references still (apparently) valid? Show(Integer(Pointer (myObjW)).ToString); Show(Integer(Pointer (myObjU)).ToString); end;
The program creates an object with two regular (ARC) references plus a weak and an unsafe one. The reference count is displayed each time. Later the two main references are set to nil (effectively freeing the object), and we look at the raw pointer value of the two "pending" references, the weak and the unsafe one. Can you guess the output without looking to the image below?
As you can see, the weak reference is automatically set to zero (so you can do an if Assigned test before using it), while the unsafe reference becomes in all effects a dangling reference, a reference to the memory location where the object used to be (before being destroyed). Using that reference is bound to incur in errors and it is very difficult to determine at run time if that reference is indeed valid. Which is the main reason for using a weak reference in the first place.
Of course, the point of using weak and unsafe references is in managing very complex data structures, but this small snippet should convey the key technical differences among these three reference types, underlying the fact that they behave in a significantly different way. I hope this blog post helps shed some light.