See Also
Variables in Objective-C
Last time we looked at writing getters and setters for Objective-C classes. Today we'll look at generating them automatically using the Variables in Objective-C
@property
and @synthesize
directives.Before Objective-C 2.0 was introduced, if you wanted to add getters and setters to a class, you wrote them yourself using instance methods, which caused some classes to become heavy with boilerplate code:
// example of hand-written getters and setters @interface Person : NSObject { int age; Address *address; NSString *name; } - (int)age; - (void)setAge:(int)anAge; - (Address *)address; - (void)setAddress:(Address *)anAddress; - (NSString *)name; - (void)setName:(NSString *)aName; // ... @end @implementation Person - (int)age { return age; } // assign-type setter - (void)setAge:(int)anAge { age = anAge; } - (Address *)address { return address; } // retain-type setter - (void)setAddress:(Address *)anAddress { if (address != anAddress) { [address release]; address = [anAddress retain]; } } - (NSString *)name { return name; } // copy-type setter - (void)setName:(NSString *)aName { if (name != aName) { [name release]; name = [aName copy]; } } // ... @endThis is a lot of code to write, and the
Person
class barely does anything yet. The @property
directive will remove some of the boilerplate code from the @interface
section of the class and the @synthesize
directive will clean up the @implementation
Declaring properties
A getter and setter form a logical property of a class. Properties typically correspond directly to instance variables, but don't have to. Sometimes a property is calculated on the fly, or the name of the instance variable is different than the name of the property. The
@property
directive replaces the getter and setter method declarations in the @interface
of the class. // declaring age property @interface Person : NSObject { int age; Address *address; NSString *name; } @property int age; // ... @endNotice that the property declaration looks a lot like an instance variable declaration. At a high level, a property is very similar to an instance variable. But as far as the compiler cares, a
@property
is simply a replacement for declaring the getter and setter methods:@property int age;is just a substitute for
- (int)age; - (void)setAge:(int)anAge;If you don't write the corresponding getter and setter methods in the
@implementation
section, you'll see compiler warnings like this:property 'age' requires method '-age' to be defined - use @synthesize, @dynamic or provide a method implementation property 'age' requires the method 'setAge:' to be defined - use @synthesize, @dynamic or provide a method implementation
Read-only properties
Sometimes, you want properties to be read-only. For example, we might store the person's birthdate instead of age:
// age property calculated on the fly @interface Person : NSObject { NSDate *birthDate; Address *address; NSString *name; } @property int age; // ... @end @implementation Person - (int)age { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDate *today = [NSDate date]; NSDateComponents *components = [calendar components:NSYearCalendarUnit fromDate:birthDate toDate:today options:0]; return components.year; } // ... @endYet it doesn't make sense to write the corresponding
setAge:
method here. If we compile this code, we'll still get nagged about the setter:property 'age' requires the method 'setAge:' to be defined - use @synthesize, @dynamic or provide a method implementationTo silence this, we need to add an attribute to the property. Property attributes go in parentheses after the
@property
keyword but before the type and name:@property (readonly) int age;Here we've told the compiler that the
age
property is readonly
, so not to worry about the setter. By default, properties are readwrite
. You can label them with the readwrite
attribute, but since it's the default, it's redundant and you'll rarely see it.Object properties
Let's set aside the calculated
age
property and go with our plain old int
version. The next property of the Person
class is address
:// declaring address property @interface Person : NSObject { int age; Address *address; NSString *name; } @property int age; @property Address *address; // ... @endWhen you compile this, you'll see warnings like:
no 'assign', 'retain', or 'copy' attribute is specified - 'assign' is assumed assign attribute (default) not appropriate for non-gc object property 'address'The compiler has noticed that
address
is an Objective-C object type and is reminding you to do the appropriate memory management. (Those lucky Mac developers don't have to worry about this any more since they now have garbage collection.) In addition to readwrite
/readonly
there's another set of property attributes: assign
, retain
and copy
. Since address
uses retain memory management, we'll change it to look like@property (retain) Address *address;At this stage,
assign
, retain
and copy
are simply documentation to other programmers about the memory management strategy for the property. If you write the setter yourself, the compiler isn't smart enough to tell if you actually wrote the correct type of setter, so be careful! There's nothing worse than code that says it's doing one thing and actually does another. So what's the point? We'll see when we get to @synthesize
.Finishing up the properties for our class, we declare a property for
name
that uses a copy memory management scheme. (Last week's post explained why we use copy
for NSString
properties.)// declaring name property @interface Person : NSObject { int age; Address *address; NSString *name; } @property int age; @property (retain) Address *address; @property (copy) NSString *name; // ... @end
Plain old pointer properties
Most Objective-C code uses objects instead of plain old C
struct
s, strings and arrays, but sometimes you'll need to use them, often when working with low level C libraries. You might be tempted to document your memory management for these plain old pointers as you would Objective-C objects:// ERROR: won't compile @interface Person : NSObject { int age; Address *address; NSString *name; char const *username; // plain old C string } @property int age; @property (retain) Address *address; @property (copy) NSString *name; @property (copy) char const *username; // plain old C string // ... @endUnfortunately, since plain old C types don't have
retain
/release
memory management like Objective-C objects, this muddies the meaning of copy
and the compiler will stop with an error like:property 'username' with 'copy' attribute must be of object typeUnfortunately, marking a property like this
assign
when your setter actually makes a copy doesn't seem right either:// not quite right @property (assign) char const *username;My advice is to leave off
assign
, retain
and copy
from properties for plain old C pointers and use a comment to note your memory management strategy.I'm out of time right now, so I'll have to finish this up next week. Coming up, the
nonatomic
property attribute, and the @synthesize
directive.
3 comments:
Something I've noticed is that you can leave out the initial declaration of a property in the class "struct" and have only the @property declaration (which seems like a good thing for DRY).
I've not been able to do this on iPhone OS; I think it only applies to recent versions of Objective-C on OS X. And I agree that having to declare both the instance variable and the property violates the DRY principle.
@Chris Ryland You're right, you can leave it out. The reason you'd want to keep it is that if you leave it out, the member variable is generated for you, you don't get access to it when you're debugging, which is a pain.
Post a Comment