@synthesize keyword explanation

Hi,

I am fairly new to Objective-C programming and I came accross the @synthesize keyword. I have a simple class called Book and in the Book.h file I declared the following:


#import <Foundation/Foundation.h>

@interface Book : NSObject

/* Declare properties here */
@property NSString *title;
@property NSString *author;
@property int pageCount;

/* Declare methods here */
-(void) setTitleForBook: (NSString *)someTitle;
-(void) setAuthorForBook: (NSString *)someTitle;
-(void) setPageCount:(int)pageCount;
-(void) printBookInfo;

@end


and in the Book.m file I used the @syntheize keyword as follows:


#import "Book.h"

@implementation Book

@synthesize title;
@synthesize author;
@synthesize pageCount;


-(void) setTitleForBook:(NSString *)someTitle {
  
   title = someTitle;
}

// other method to follow....

@end


I am using the @synthesize keyword, but I dont really understand fully why, I know it has to do with getter and setter. Could some explain the reason to use this keyword and what is happening when this happens?


Thanks.

Replies

The short answer: you need to have explicit @synthesize statements because you've written methods assuming certain names for the instance variables backing your properties, but you haven't manually declared such instance variables with those names.


The much longer explanation:


Generally speaking, there are two parts of a "property". I'm speaking here informally about properties, not specifically about "declared properties" which is what the @property keyword introduces.


There's the interface part and the private implementation part.


In the interface, a property consists of accessor methods. For a simple property, this will usually include a getter method. Also, if the property is not read-only, it will include a setter method. Of course, these accessor methods reflect the type of the property.


Properties can be implemented in many ways, but the most common way is for them to be associated with a similarly-named instance variable to use for storage and for the accessor methods to read and write that storage.


All of this could be done manually, and that's how we used to have to do it. So, for example, a hypothetical Person class might have a "firstName" property. The programmer would declare the accessor methods manually:

- (NSString*) firstName;
- (void) setFirstName:(NSString*)newName;


Then, they'd need to provide the implementation manually, too. For example, this would be in the declaration of the class to declare the instance variable:

@interface Person : NSObject
{
@private
    NSString* _firstName;
    …
}


The leading underscore in the instance variable name is a convention used to make it clear that it's an instance variable (as opposed to a local, parameter, or global).


And this would be in the implementation:

- (NSString*) firstName
{
    return _firstName;
}

- (void) setFirstName:(NSString*)newName
{
    _firstName = newName;
}


That's a fair amount of tedious boilerplate code for every property. And that's even after omitting memory management, which we used to have to do manually, too, because these days there's ARC.


These days, there's a lot of help from the compiler to avoid the need to write this boilerplate code. First, there's declared properties with the @property keyword. The declaration:

@property NSString* firstName;

is equivalent to the declarations of the two accessor methods, -firstName and -setFirstName:, I showed above in my first code snippet.


Using a declared property for the interface but implementing the property manually by writing the accessor implementations yourself is perfectly fine. It's important to the users of your class that your implementation match the semantics of the declared property, of course. If you manually implement the accessors, then you are responsible for implementing whatever details (such as an instance variable) that the accessor implemenations need.


Alternatively, another way the compiler can help is to synthesize the implementation of the properties. There are two modes for property synthesis, automatic and explicit. If you neither manually implement the accessor methods nor "@synthesize" the property, then the compiler will automatically synthesize it. It will generate an instance variable for the property's storage, using the naming convention with the leading underscore, or use an instance variable of that name if you explicitly declared one. It will also generate the accessor methods with the proper semantics to match the @property declaration.


If you explicitly synthesize a property with the @synthesize keyword, then the compiler will do something similar but slightly different. If your @synthesize statement doesn't explicitly associate an instance variable name with the property, then it will use a variable name without a leading underscore. So:

@synthesize firstName;

generates an instance variable named "firstName", not "_firstName", and uses that in the accessor implemenations it provides. To use the conventional name, you'd need to be explicit:

@synthesize firstName=_firstName;


The second difference is that explicit synthesis can be combine with manual implementation. The compiler will provide the pieces (instance variable, getter, setter) that you do not.



Now, to the code you showed. You have declared a property "title". You have implemented a method -setTitleForBook:. Technically, these two have nothing to do with one another. -setTitleForBook: is not a setter for the property. Because you didn't override the default setter in the @property declaration, that tells both users of your class and the compiler that the setter has the conventional name, -setTitle:. Then, when the compiler synthesizes the property, it's generating an implementation of -setTitle:, too. That's probably going to get you in trouble down the line, because some user of this class is going to write

someBook.title = @"A Good Story";

and you're going to be surprised when that invokes the synthesized -setTitle: method and not your -setTitleForBook: method, where you may have put important code. The same thing goes for -setAuthorForBook:.


You could address this in two ways. One solution would be to inform users of your class and the compiler about this by changing the property declaration to:

@property (setter=setTitleForBook:) NSString *title;


Another solution would be to use the conventional name. My recommendation is to use the conventional name. It's not necessary to include "ForBook" in a setter on a class named "Book".


It's not clear from the partial code you provided if you supplied a manual implementation of the getter for the title property. If you did, then you inhibited automatic synthesis and, without explicit synthesis, there would be no instance variable for it. If you did not provide a getter, then the property was being automatically synthesized (because there was no setter that the compiler recognized as such). In that case, there was a synthesized instance variable but it had a name that you weren't expecting ("_title" rather than "title"), so your manually-provided code wouldn't compile. That's why you thought you "needed" explicit synthesis.


Once you address the issue of the property setter name, you're manually providing implementations for at least some of the methods. That's fine if you want to do something more than the standard work of a setter. If you're just attempting to implement the basic functionality, then it's probably better to let the compiler synthesize the accessors.


By providing your own accessor implementations, you're inhibiting automatic synthesis of the properties. That means, if you want any synthesis of the remaining parts (any of instance variable, getter, or setter), you have to explicitly tell the compiler to synthesize those using an @synthesize statement. You're doing so, which is fine, but with explicit synthesis the compiler will use the unconventional instance variable name without the leading underscore. So, I recommend that when you explicitly synthesize that you always override the instance variable name with the conventional one. For example:

@synthesize title=_title;


Of course, you need to take this instance variable name into account as you code your methods.