Synthesizing Properties
Welcome to another episode of Objective-C Tuesdays. Today we
pick up
where
we left off and dive into the @synthesize directive
that tells the compiler to write getters and setters automatically.
We already saw that the @property directive can shrink the
boilerplate code in a class @interface declaration
somewhat. We started with this:
@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
And by using properties, we end up with this:
@interface Person : NSObject {
int age;
Address *address;
NSString *name;
}
@property int age;
@property (retain) Address *address;
@property (copy) NSString *name;
// ...
@end
It's not a giant difference, but it's an improvement. As well as
making the code more compact, it separates the logical properties of
the class from the rest of the methods. Where @property
really starts to pay off is when we combine it with
@synthesize. The @implementation for the
Person class looks like this:
// hand written getter and setter methods
@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];
}
}
// ...
@end
That's a lot of boilerplate code to churn out, and memory management in
the retain and copy type setters makes it more error prone than simple
assign type setters. It's just begging to be machine generated, so
let's do that:
// synthesized getter and setter methods
@implementation Person
@synthesize age, address, name;
// ...
@end
All that onerous code can be deleted, and the compiler now generates
the correct type of getters and setters based on the attributes of the
corresponding @property directive. Note that to use
@synthesize, you must have a corresponding
@property in the @interface section. I like
to put the property names in a list after @synthesize, but
you can have multiple @synthesize lines if you like, with
one or several property names per line:
@implementation Person
@synthesize age;
@synthesize address, name;
// ...
@end
If you need to provide a particular getter or setter yourself, but you
want the compiler to write the rest of them, you simply add it to your
@implementation. The compiler looks first to see what you
provided before it generates anything. So if we need to do a check
when setting the age of a Person instance, we
simply write our own setter:
// synthesized getter and setter methods
// with one custom setter
@implementation Person
@synthesize age, address, name;
- (void)setAge:(int)anAge {
age = anAge;
if (age >= 55) {
[[JunkMailer sharedJunkMailer] addPersonToAARPMailingList:self];
}
}
// ...
@end
This is great, but sometimes you want the property to have a different
name than the instance variable that backs it up. For example, we
might call the instance variable ageInYears, but want to
call the property age:
@interface Person : NSObject {
int ageInYears;
// ...
}
@property int age;
// ...
@end
@implementation Person
@synthesize age, address, name;
// ...
@end
This confuses the compiler and it complains:
synthesized property 'age' must either be named the same as a compatible ivar or must explicitly name an ivar
So how do we tell it to use the instance variable
ageInYears when generating the age property?
The @synthesize directive has one modifier, which solves
our problem:
@implementation Person
@synthesize age = ageInYears, address, name;
// ...
@end
That wraps up normal usage of @synthesize. Next week, we
look at one attribute of @property that we've neglected so
far:
nonatomic.