Avoid Objective-C Bugs w/Properties and Instance Variables (ivar)

25 Nov

Objective-C is the programming language of choice for iOS an Mac OSX programming, so becoming proficient in native programming for those platforms is essential to building great applications. Apple emphasizes that programmers must understand the “retain-release” model of how to manage objects. True, but this is not enough. Unfortunately, Objective-C makes it exceedingly easy to inadvertently write code that breaks the retain-release model, leading bugs that cause programs to crash.  You can adopt some practices to avoid such problems.

Retain-release is a mechanism for managing reference counts on a dynamically allocated object. It is common for an object to refer to another via its instance variables (or as Apple development documentation likes to call them, “ivars”). Instance variables and “properties” are conceptually equivalent, and most Apple docs assume you understand the distinction, so it is often not clear that there is a distinction. In fact, most of the time your program should be using properties (and not ivars, directly). The problem is that Objective-C makes it easier to refer to an ivar than its property, making it exceedingly easy to  bypass important functionality associated with the property—functionality that implements important retain-release calls.

Objective-C Instance Variables (ivars) vs. Properties

Objective-C claims to be a pure superset of C. An instance variable is exactly the same as a C struct member data item; it is where a data items is stored and there is no functionality implicitly associated with that data item. In Objective-C, an instance variable is declared like this:
@interface MyClass
   NSObject *objValue;

Within a method of the class, access to instance variables follow normal C/C++ syntax which allows them to be, simply, referenced by their name:

self->objValue = obj;    // Explicit reference to ivar; which is the same as
objValue = obj;          // Implicitly assumed reference to ivar

Objective-C  allows a “property” to correspond to a class’s instance variable:

@interface MyClass
    NSObject *objValue;

@property (retain) NSObject *objValue;

@implementation MyClass
@synthesize objValue;
The Objective-C compiler injects “accessor” code (i.e., getter/setter methods) to take care of  (retain/release) reference counting and other rote, complex, tedious tasks. The compiler generated code alleviates you from having to write such complex code—which helps to avoid crashes due to retain-release errors. So it is best to use properties rather than accessing the ivars directly. Referencing a property can be written as (note the dot ‘.‘ rather than ‘->‘, shown above):
self.objValue = obj;
While you should use properties rather than values, it is all to easy to make the mistake of referring to the value as an ivar rather than the property (as shown above):
objValue = obj;                 // Use the ivar—does not use property's setter

Since it is so easy to refer to the instance variable rather than the desired property, use distinct names for each ivar and its property to avoid inadvertent references to the ivar:

@interface MyClass
   NSObject *_objValue;         // Name, distinct from its property's name

@property NSObject *objValue;   // Common, property name

@implementation MyClass
@synthesize objValue=_objValue; // Associate the ivar w/property

Then, references to the property-name, alone, will result in a compiler error:
objValue = obj;   // !!! Yields a compiler error since there's no ivar of that name.

Apple ought to get off their “superset of C” horse since they’ve made so many changes already and change the meaning of unadorned symbol names to default to the property, if it is declared. This would break some code, but they already make a distinction between C/C++, Object-C, and mixed C/C++ and Object-C source files, this should be included in that distinction.

Example: Prove the Difference Between ivars and Properties

So, let’s demonstrate this. To distinguish whether we are referring to the instance variable or its property, we will check whether each property’s accessors is called by checking the reference count to an object after assignment—assignment invokes the setter access method and changes the reference count:
@interface MyClass : NSObject
   // Declare instance variables
   NSObject *objValue;     // This can be any kind of NSObject
   NSObject *objValue2;    // Second object
   NSObject *objValue3;    // Third object
// Declare properties
@property (retain) NSObject *objValue;
@property (retain) NSObject *objValue2;
@property (retain) NSObject *objValue3;
@property (retain) NSObject *objValue4;

- (void)testSymbolAccess;    // Method to test this out

@implementation MyClass
   // Create accessor methods for properties (not necessary for Xcode 4 w/LLVM
   @synthesize objValue;
   @synthesize objValue2;
   @synthesize objValue3;
   @synthesize objValue4;    // No explicit ivar (created by Xcode 4 automatically)

- (void)testSymbolAccess
   NSObject *obj = [[NSObject alloc] init];        // Implicit retain++ (i.e., 1)

   // Add ref counts to avoid the count == 0 for the following sequence of tests.
   [obj retain]; [obj retain]; // retain += 2 (i.e., 3)
      NSLog(@"1.  Ref count %d\n",[obj retainCount]); // Baseline

   // "Normal" property usage:
   self.objValue = obj;        // Invoke accessor: retain++ (implicit retention)
      NSLog(@"2.  Ref count %d\n",[obj retainCount]);
   self.objValue = nil;        // Invoke accessor: retain-- (as it sets ref to nil)
      NSLog(@"3.  Ref count %d\n",[obj retainCount]);
   self.objValue = obj;        // Invoke accessor: retain++ (implicit retention)
      NSLog(@"4.  Ref count %d\n",[obj retainCount]);

   // Explict ivar reference:
   self->objValue2 = obj;      // Direct access to ivar; ref count untouched
      NSLog(@"5.  Ref count %d\n",[obj retainCount]);
   self->objValue2 = nil;      // And resetting it does nothing.
      NSLog(@"6.  Ref count %d\n",[obj retainCount]);

   // So what does a bare reference do?
   objValue3 = obj;            // Which does this refer to, ivar or property?
      NSLog(@"7.  Ref count %d\n",[obj retainCount]); // ivar! (ref count is unchanged)

   self.objValue3 = nil;       // Explicty remove ref via property (retain--)
      NSLog(@"8.  Ref count %d\n",[obj retainCount]); // The ref count is out of sync

   self.objValue = nil;        // Remove reference: retain--
      NSLog(@"9.  Ref count %d\n",[obj retainCount]); // We're really out of sync!

   // And starting w/Xcode 4.0, you needn't specify an ivar
   objValue4 = obj;            // Which does this refer to, ivar or property?
      NSLog(@"10. Ref count %d\n",[obj retainCount]); // ivar! (ref count is unchanged)

   self.objValue4 = nil;       // If, later I explctly clear property (retain--)
      NSLog(@"11. Ref count %d\n",[obj retainCount]); // Ref count is super out of sync

Calling testSymbolAccess

MyClass *test=[[MyClass alloc] init];
[test testSymbolAccess];


2011-11-25 17:50:45.006 [8135:207] 1.  Ref count 3
2011-11-25 17:50:45.008 [8135:207] 2.  Ref count 4
2011-11-25 17:50:45.008 [8135:207] 3.  Ref count 3
2011-11-25 17:50:45.008 [8135:207] 4.  Ref count 4
2011-11-25 17:50:45.009 [8135:207] 5.  Ref count 4
2011-11-25 17:50:45.010 [8135:207] 6.  Ref count 4
2011-11-25 17:50:45.010 [8135:207] 7.  Ref count 4
2011-11-25 17:50:45.011 [8135:207] 8.  Ref count 3
2011-11-25 17:50:54.558 [8135:207] 9b. Ref count 2
2011-11-25 17:51:35.019 [8135:207] 10. Ref count 2
2011-11-25 17:51:41.753 [8135:207] 11. Ref count 1

Enhanced by Zemanta


Tags: , , , , ,

%d bloggers like this: