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.