Avoiding a Common Objective-C Mistake

Wednesday, July 6th, 2011

There is one particular mistake that stands out to me as the most common error made by beginning iOS developers (even above misuse of auto-release pools). It essentially boils down to a (quite forgivable) lack of understanding of how properties and reference counting work within Modern Objective-C.


The following code illustrates the problem:


@interface MyObject : NSObject {}
@property (nonatomic,retain) UIViewController * myVc;

- (BOOL) init;
- (VOID) dealloc;

@end

@implementation MyObject
@synthesize myVc;

- (BOOL) init
{
    self.myVc = [[ UIViewController alloc ] init ];

    return YES;
}

- (VOID) dealloc
{
    [ myVc release ];
}

@end

For the purposes of this explanation, let us examine this partial implementation of MyObject. In this case, we assume that the developer wishes to call init precisely once during initialization of the app. Let us furthermore assume that the developer will call release for this object, and expect a dealloc call to be sent in order to free up our resources.

Can you spot the error?

The issue is that a release call passed to an instance of MyObject will result in a memory leak. To understand why, let's briefly review properties with respect to reference counting.


Properties and Reference Counts


In the case of our MyObject implementation, we have a single property, myVc, which will roughly distill down to the following pseudo-code, depending upon the property declaration, upon compilation:



- (UIViewController *) myVc;

- (VOID) setMyVc: (UIViewController *) newVc
{
    …

    [ myVc release ];

    myVc = newVc;

    [ myVc retain ];

    …
}

To use this property, you may either directly access these generated functions, or use dot selection and allow the compiler to substitute the access with the appropriate getter or setter.

Thus when a property is set with a value of nil, the previously stored object of the property is released (and potentially deallocated), the property is assigned the value of nil, and we attempt to retain nil (a harmless operation).


Redundant Retaining


Based on our above pseudo-code for our setter, let's examine the result of MyObject's init function. Firstly, a new UIViewController object will be allocated and initialized. This object will possess a reference count of 1.

Next, our property myVc is set with the address of our newly constructed view controller. At this point our setter is invoked, which will increment the retain count on the view controller. Thus at this point our view controller object has a retain count of 2, which is easily verified through the debugger as shown below.

Note a quick caveat - the following method for checking reference counts in the debugger is extremely error prone. Do not use and/or rely upon this method (particularly in multi-threaded environments).

(gdb) print (int) [ myVc retainCount ]
$1 = 2
(gdb)

This is clearly problematic as the deallocation of MyObject will only decrement the reference count of myVc by one, and will therefore never invoke the view controller's deallocation routine. Once MyObject is deallocated, we will lose any reference to our dynamically allocated view controller, as it was never appended to any auto-release pool.


Potential Remedies

Luckily, there are a number of easy ways to resolve this issue, including the following:


Another option, which is not recommended, would be to set self.myVc to nil within MyObject's dealloc routine (in addition to the release). This practice is discouraged due to the facts that:


Note the distinction between the use of self.myVc within a dealloc routine, and simply myVc. The former (discouraged case) will invoke a setter, leading to the potential hazards listed above, while the latter will not invoke the setter and merely sets the instance variable myVc to a value of nil.



Conclusion

Once we've repaired the issue, we can check the validity of the fix in the debugger as follows:

(gdb) print (int) [ myVc retainCount ]
$1 = 1
(gdb)

Thus upon final release and deallocation of MyObject, a single call to release myVc will reduce its reference count to zero, and trigger its deallocation as well.

Thanks for reading! As always, comments questions and concerns are always appreciated, especially if you notice any errors or necessary corrections.