Why am I starting with this subject? This topic wasn’t immediately intuitive and required some digging… plus it’s a vital subject. Thoroughly understanding how a language handles object lifetime management (OLM) is a “must” for any software engineer. The biggest, most obvious benefit is allowing one to write code without memory leaks or distractions. What I mean by distractions is mental effort taken away from addressing the problem at hand and putting it towards something as trivial as actually writing the code. Ideas should flow out of the brain, through the fingers, and onto the keyboard without worrying about routine things like object instantiation and destruction.
Note that the official documentation must be referred to for details. These are only notes.
OLM and Delphi
The Delphi language presents the programmer with two mechanisms. The basic mechanism is wholly manual: explicitly allocating and freeing an object. You, the programmer get to do all the work. This was the original paradigm for objects when the language first supported them.
var lFoo :TFoo; begin lFoo := TFoo.Create; try ... work with object ... finally lFoo.Free; end; end;
The second mechanism the Delphi language presents is using interfaces and reference counting. Interfaces were added to the language later, I believe with Delphi 2. There’s some great syntactic sugar in the compiler that almost allows one to forget about OLM once an object instance has been tied to an interface. The compiler generates code to implicitly call _AddRef and _Release automatically. I say almost because while it correctly addresses >99% of OLM, there are time where it’s beneficial to circumvent the default behavior.
var lFoo :IFoo; begin lFoo := TFoo.Create; ... work with object ... end;
In Delphi, to take advantage of interfaces, one must use classes properly equipped to support them. Ordinarily one will descend from TInterfacedObject to pick up this behavior. The root object, TObject, has no capability to work with interfaces or reference counting. This is inconvenient sometimes, but interfaces and reference-counted OLM were later additions to the language.
OLM and Objective-C
Objective-C has a different history. Its intent from the start was to extend C, giving it object-oriented capabilities. Reference-counted OLM is part of the paradigm from the ground up. Its root object, NSObject, supports reference counting. As a result this is passed to every other object.
I found a great explanation of things here. Consider it required reading. The rest are just my re-interpretation of some of the concepts in that document that I find important to remember. In other words, they stand out as important divergences from Delphi.
So what do we have thus far?
Concept | Delphi | Objective-C |
---|---|---|
Reference Counting Class | Objects (typically) descending from TInterfacedObject and bound to an interface. | All objects.* |
Increment Reference Count | _AddRef (normally called implicitly) | retain |
Decrement Reference Count | _Release (normally called implicitly) | release |
Reference Counts Maintained | automatically | manually |
*All objects descend from the class NSObject — the Objective-C equivalent of TObject.
So, some good news and some bad news. We gain reference counting in all objects, but lose the automatic maintenance of the reference counts. So how do we maintain the reference counts? By adhering to a set of rules, and a little glue. First the rules. They’re similar in spirit to the original Delphi OLM.
If | Then | Therefore |
---|---|---|
If you allocate it… | you own it, | therefore you must call release. |
If you didn’t allocate it… | you don’t own it, | therefore you must not call release. |
If you didn’t allocate it… | but you want to keep it around, | you must call retain and release at the appropriate times. |
Note that the “appropriate times” are the same times that one might manually call _AddRef() and _Release() in Delphi to keep a reference-counted object around (without using interfaces).
The specific rules are summarized here. Note that there are conventions for method naming that should be adhered to to avoid problems later.
Autorelease Pools
There are cases where reference counting can be a bit tricky. In Delphi these are ordinarily hidden from view through compiler magic. In Objective-C there’s an interesting work-around: autorelease pools. (See link for official documentation.) The autorelease pools are a stack-oriented hierarchy of object-specific memory pools to which objects may be attached. One attaches objects to them to keep them around for disposal at a future time. When one of these pools are released, all objects with a zero reference count are freed, and all other objects are moved to the next pool in the stack.
One can use (in interesting ways) autorelease pools to manage when autorelease objects are released.
Summary
- You own the object if you call alloc*, new*, or *[Cc]opy*. Be sure to call release.
- If you otherwise received an object, autorelease has already been called on it, pushing onto the autorelease pool. It is safe to use in your scope (as the caller).
- If you wish to keep a handle to an object “otherwise received” (see #2), call retain. When appropriate, dispose of the handle via release or autorelease as appropriate.
Leave a Reply