@Property And @Synthesize
Last time we looked at
writing
getters and setters for Objective-C classes. Today we'll look at
generating them automatically using the @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];
}
}
// ...
@end
This 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;
// ...
@end
Notice 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;
}
// ...
@end
Yet 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 implementation
To 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;
// ...
@end
When 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
// ...
@end
Unfortunately, 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 type
Unfortunately, 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.