Tuesday, May 4, 2010

Objective-C Tuesdays: 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.

8 comments:

Cooper said...

Thanks for doing the Objective-C Tuesdays series. I have read a lot the Apple Programming Guides and have created an iPhone app for my company, but because of how I have thrown myself in to the language to quickly learn and get an app off and running there are certain pieces that I miss - such as @property and synthesize. I use these all the time, and understood the end result but didn't realize the separation till reading this article. Thanks again.

Don McCaughey said...

Thanks Cooper! I think most iPhone developers dove into Objective-C and Cocoa Touch head first (I know I did when I started in 2008) and have to backfill their knowledge of the language as they move forward.

crankyalien said...

Hm, I still don't understand the benefit of naming a property differently than the instance variable it represents. Could you give an example where it makes sense to do so? Thanks.

Don McCaughey said...

Personally, I prefer the property and instance variable names to be the same. The most common example of changing the property name might be for a BOOL instance variable such as "active". Some coders would prefer to see code like "if (myobject.isActive) { ... }" instead of "if (myobject.active) { ... }". I don't think that "isActive" is a big improvement over "active", but some people prefer it. I'd rather have the naming pattern be consistent without any special cases.

fedmest said...

Hi Don,

This is a great series! Thanks for the good work! I have a question regarding @synthesize. I have noticed that - contrary to what I'm reading here - Objective C seems to also generate the ivar for a property if it's not there in the @interface.

Have things changed since your post or did I get something wrong?

Thanks,

Fed

Don McCaughey said...

Thanks Fed!

Apple created a "modern runtime" for Objective-C several years ago that will automatically generate instance variables. The modern runtime has been available for 64-bit OS X apps for a while, and is available on iOS starting with 4.0. If your iOS deployment target is 4.0 or later, then you can use this feature. If you're still targeting older versions of iOS, you'll need to explicitly declare all your instance variables.

Given that most iOS users upgrade their OS pretty rapidly, at this date, most users are running iOS 4.0 or later. We will probably change our iOS deployment target to 4.0 later this year and be able to take advantage of all the nice 4.0 features.

fedmest said...

Thank you Don, it makes perfect sense. Now you mention I remember finding that this feature had an erratic behaviour (sometimes it wouldn't work). I have a few projects that are still on iOS 3.0, so most likely that's why I was finding inconsistent behaviour. Do you know by any chance if the ivar that is injected into the struct is protected or private or whatever and whether there is a way of influencing that? It's not vital, don't go looking for an answer if you don't know off the top of your head ;-)

And again my congrats for this great blog!

Don McCaughey said...

I think the created ivars are private by default, but I don't recall for sure if I read that and I don't remember there being any way to change the visibility. I think it's assumed that if you're creating the properties, you want to hide the ivar.