RSS
 

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:

[sourcecode language=”objc” light=”true”]
@interface MyClass
{
NSObject *objValue;
}
@end
[/sourcecode]

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:

[sourcecode language=”objc” light=”true”]
self->objValue = obj;    // Explicit reference to ivar; which is the same as
objValue = obj; // Implicitly assumed reference to ivar
[/sourcecode]

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

[sourcecode language=”objc” light=”true”]
@interface MyClass
{
NSObject *objValue;
}

@property (retain) NSObject *objValue;
@end

@implementation MyClass
@synthesize objValue;
@end
[/sourcecode]

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):

[sourcecode language=”objc” light=”true”]
self.objValue = obj;
[/sourcecode]

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):

[sourcecode language=”objc” light=”true”]
objValue = obj;         // Use the ivar—does not use property’s setter
[/sourcecode]

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:

[sourcecode language=”objc” light=”true”]
@interface MyClass
{
NSObject *_objValue;       // Name, distinct from its property’s name
}

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

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

@end
[/sourcecode]

Then, references to the property-name, alone, will result in a compiler error:

[sourcecode language=”objc” light=”true”]
objValue = obj;   // !!! Yields a compiler error since there’s no ivar of that name.
[/sourcecode]

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:

[sourcecode language=”objc” light=”true”]
@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
@end

@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
}
@end
[/sourcecode]

Calling testSymbolAccess

[sourcecode language=”objc” light=”true”]
MyClass *test=[[MyClass alloc] init];
[test testSymbolAccess];
[/sourcecode]

yields:

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: , , , , ,