iPhone developers who have experience with Macintosh development have probably long since become comfortable with the whole "property" notion in Objective C. For iPhone developers who are recent converts from other mobile environments (such as Java or Symbian), this can be a bit twistier. In the hopes that some people won't repeat my pain, here's a brief discussion of variables and properties, along with the implications to proper memory management.
Let's start with a simple class:
Code:
//MyClass.h file
@interface MyClass: NSObject
{
NSString *text;
}
-(void) init;
-(void) logText;
@end
//MyClass.m file
@implementation MyClass
- (void)init
{
text = @"some text";
}
- (void)logText
{
NSLog(@"%@", text);
}
@end
This is pretty straight-forward. We have a simple class, which has a single member variable named "text" which is a pointer to an NSString. The variable is initialized in the "init" function, and used in the "logText" function.
Now, at present, the value that's stored in "text" is a constant, so we don't have to worry about memory management. In any real application, however, we'd probably want the ability to change the value of "text" at runtime. One way we can do this is to introduce getter and setter methods:
Code:
//MyClass.h file
@interface MyClass: NSObject
{
NSString *text;
}
-(void) init;
-(void) logText;
-(NSString*) text;
-(void) setText:(NSString *)textValue;
@end
//MyClass.m file
@implementation MyClass
- (void)init
{
text = @"some text";
}
- (void)logText
{
NSLog(@"%@", text);
}
-(NSString*) text
{
return text;
}
-(void) setText:(NSString *)textValue
{
if (textValue != text)
{
[textValue retain];
[text release];
text = textValue;
}
}
-(void)dealloc
{
[text release];
[super dealloc];
}
@end
So, we've done the following:
First, we've added a member function that allows us to retrieve the current text value like so:
Code:
NSString *theTextValue = [obj text];
(assuming that "obj" was an instance of MyClass that we previously obtained). This function is pretty obvious - it just returns the current text value.
Second, we've added a member function that allows us to change the current text value like so:
Code:
[obj setText: newStringValue];
(assuming that "obj" is an instance of MyClass and "newStringValue" is an instance of NSString).
Now, we can't be certain that the string value that will be passed in will be a constant - it may be any NSString value, including ones that have been allocated off the heap. Because of this, we need to "retain" the object that we're being passed to ensure that it survives for at least as long as the instance of MyClass does. Similarly, since we're not going to be holding onto the old value that's being replaced, we need to release that value. The "if" in "setText" just cuts out unnecessary work in the case where the object being set is the same object as the one we're holding onto. Such things can happen on occasion depending on how the code is used, so it's always best to consider these cases. Of course, we could have just written:
Code:
-(void) setText:(NSString *)textValue
{
[textValue retain];
[text release];
text = textValue;
}
This would have worked just as well in the "same object case," because we would first increment the retain count and then decrement it. Note that the following code is
wrong:
Code:
-(void) setText:(NSString *)textValue
{
[text release];
[textValue retain];
text = textValue;
}
In this case, in the situation in which the "input" value and the "held" value happened to point to the same instance of NSString, the string would end up being deallocated - the first line would probably decrement the retain count to zero, resulting in the object being returned to the heap. The second line would then try to retain this now-dead object. Bad mojo. Not necessarily likely, but it's a bug waiting to happen someday.
Finally, note that we've implemented a "dealloc" function in which we release the text object. Basically, when we die, we need to return all our books back to the library, so if we're holding onto something, we need to release it.
It's worth pointing out that all of this works perfectly well if the value set is "nil." Objective C has a wonderful feature that allows you to send messages (such as release and retain) to "nil" without things blowing up. This is similar to the fact that you can "delete" a null pointer in C++ without harm.
Now, just for fun, we're going to add a second member variable, along with its getters and setters. We'll see why in a minute.
Code:
//MyClass.h file
@interface MyClass: NSObject
{
NSString *text;
int value;
}
-(void) init;
-(void) logText;
-(NSString*) text;
-(void) setText:(NSString *)textValue;
-(int) value;
-(void) setValue:(int*)intValue;
@end
//MyClass.m file
@implementation MyClass
- (void)init
{
text = @"some text";
value = 2;
}
- (void)logText
{
NSLog(@"%@", text);
}
-(NSString *) text
{
return text;
}
-(void) setText:(NSString *)textValue
{
if (textValue != text)
{
[textValue retain];
[text release];
text = textValue;
}
}
-(int) value
{
return value;
}
-(void) setValue:(int)intValue
{
value = intValue;
}
-(void)dealloc
{
[text release];
[super dealloc];
}
@end
Here, we've simply added in integer member variable. We have the same kind of getter and setter methods as before. Here, however, we don't have to do the "retain" and "release" stuff, because the integer isn't allocated off the heap - it's just stored within MyClass. So, now we can do:
[code]
NSString *s = [obj text];
[obj setText:@"new string"];
int i = [obj value];
[obj setValue:3];
[code]
and so forth.
Now, getters and setters like this are so incredibly common that the folks that build Objective C probably got tired of writing the same methods over and over. They're also incredibly cookie-cutter - the same lines over and over again. As a result, they provided some shortcut methods that will write these methods for us. Let's take advantage of this by rewriting the code as:
Code:
//MyClass.h file
@interface MyClass: NSObject
{
NSString *text;
int value;
}
@property(nonatomic, retain) NSString *text;
@property(nonatomic, assign) int value;
-(void) init;
-(void) logText;
@end
//MyClass.m file
@implementation MyClass
@synthesize text;
@synthesize value;
- (void)init
{
text = @"some text";
value = 2;
}
- (void)logText
{
NSLog(@"%@", text);
}
-(void)dealloc
{
[text release];
[super dealloc];
}
@end
Here's what we've done:
First, we've removed the definitions of the two getters and two setters from the MyClass.h file. Then, we've added two lines starting with "@property".
In MyClass.m, we've removed the implementation of the two getters and two setters, and added two lines starting with "@synthesize".
This block of code is functionally identical to the previous block of code. If you think about it, writing a getter is a no-brainer. All you need to know is what variable you're returning and what type it is. Given that information, it's easy to crank out the code. Similarly, for a setter, all you need to know is what variable you're setting, what type it is, and whether you're going to be simply assigning the value (as in the "int" case) or whether you're going to be going through the retain/release procedure (as in the "NSString" case).
Thus, the four "@" lines translate as follows:
@property(nonatomic, retain) NSString *text; translates as
"I have a member variable of type NSString* named 'text'. I will want a getter/setter pair that uses the retain/release procedure."
@property(nonatomic, assign) int value; translates as
"I have a member variable of type int named 'value'. I will want a getter/setter pair that doesn't use the retain/release procedure - just assign it."
@synthesize text; translates as
"Please automatically create the code for the getter and setter for 'text'"
@synthesize value; translates as
"Please automatically create the code for the getter and setter for 'value'"
Note that we are still responsible for doing the "release" in the "dealloc" method. Unfortunately, the Objective C gurus apparently didn't come up with a way to write that code for us.
Part II of this moves on to "property notation"