I’ve been working in Objective-C for a little while now; not quite two years, off and on. I was really excited when Apple announced that Objective-C 2.0 was going to have generated properties, but the syntax they gave us leaves me flat, as it is needlessly verbose.
For those who don’t know, in Objective-C 1.x, if you had an instance variable in a class that you wanted to expose, you had to provide getter and setter methods for it, just like you do in Java, C++ and several other OO languages. You would see something like this in MyClass.h:
@interface MyClass : NSObject { NSString *name; } - (void) setName: (NSString *) aName; - (NSString *) name; @end
and then in MyClass.m, you would see this:
#import "MyClass.h" @implementation MyClass - (void) setName: (NSString *) aName { [aName retain]; [name release]; name = aName; } - (NSString *) name { return name; } @end
Objective-C 2.0 promised to eliminate all that boilerplate code in your *.m files for getting and setting variables. But they did it in a strange way. Now, in MyClass.h, you would see this:
@interface MyClass : NSObject { NSString *name; } @property(nonatomic, retain) NSString *name; @end
and then in MyClass.m, this
#import "MyClass.h" @implementation MyClass @synthesize name @end
Now, it certainly cut out quite a bit of code for the getter and setter, but why do I have to declare the type of the property twice? You have to declare the instance variable as usual, but then you also have to specify the data type again when you add the @property declaration. There’s no reason I can think of that those two lines couldn’t have been combined into the variable declaration. Objective-C already has tokens that are ignored, such as IBOutlet, so it shouldn’t have been an issue with breaking the parser. And the @synthesize declaration in the *.m file is annoying, but I guess it was necessary to keep the properties from being auto-created in the wrong place. In my opinion, this is what property declarations should look like
@interface MyClass : NSObject { @property(nonatomic, retain) NSString *name; } @end
That’s it. No duplication. Simple. Elegant.
Can anyone think of a good reason why they didn’t do it like that?
12/28/2008 15:13:23 Update: As Ahruman pointed out in his comment, I misspoke about IBOutlet. It is not actually ignored, but is used to tag an instance variable for use by Interface Builder. Sorry for the confusion. And be sure to read his comment below. It’s packed with good info that I didn’t know.
The fundamental reason is that the ivar and the property are separate entities. The property is part of the class’s interface, while the ivar is an implementation detail, as is the connection between them expressed by the
@synthesize
directive. It is a fundamental, er, property of properties that you can change their implementation (or override it in a subclass) without changing the interface.This points at a wart in Objective-C: the ivars of a class are (generally) implementation details, but are part of the
@interface
block. This is because, in the traditional Obj-C runtimes, ivars were accessed as fixed offsets from theself
pointer, meaning that subclass implementations had to know about the ivar layouts of their superclasses which thus had to be public.This is fixed in the new non-fragile runtime, as used in 64-bit Mac OS X apps and all iPhone apps. When targeting the new runtime, you don’t need the ivar declaration (as long as you don’t access the ivar directly), and just need the
@property
declaration in the@interface
block and the@synthesize
directive in the@implementation
block (or an explicit implementation not using an ivar, or@dynamic
).The wart still exists, since you can’t declare ivars in the
@implementation
block in the non-fragile runtime except for synthesized ivars. Feel free to dupe my bug on the issue.As a side note,
IBOutlet
isn’t really ignored by the parser; it’s an empty preprocessor macro, so it’s removed before anything Obj-C-specific sees it.Thanks for the info, Ahruman. There’s a lot there that I didn’t know.
Good post! I, too, had a tough time getting the “ivar=implemenation detail, @property=interface, @synthesize=link between them” concept through my head. Something that really helped me understand it, though, was seeing how to use properties with ivars when their names don’t match.
Note in the following example that the ivars use underscore prefix (which makes them easy to discern from other, non-ivar variables):
@interface MyClass : NSObject {
@private
// Private ivar needs getter/setter to be accessible
UITextField *_textField;
}
// Note that the property name does NOT have the underscore prefix
@property (nonatomic, retain) IBOutlet UITextField *textField;
This shows how the @synthesize tag is the “bridge” that connects the ivar to the property (i.e., automatic method generation):
@implementation
...
// Generate textField: and setTextField: methods for _textField ivar
@synthesize textField=_textField;
...
@end
As another sidenote (similar to Ahruman’s), it’s interesting to note that the IBAction “type qualifier,” like IBOutlet, is also pre-processor macro defined by UIKit in UINibDeclarations.h:
#ifndef IBAction
#define IBAction void
#endif
This means that
- (IBAction)someMethod:(id)sender;
gets turned into- (void)someMethod:(id)sender;
.