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
.