After going through a beginner's iPhone developer book and reading sample code online, I've noticed that most Objective C programmers synthesize nearly every instance variable. Some variables are convenient to snythesize, but most should not when honoring the object oriented principle of encapsulation. The worst are synthetized properties marked as private. A C++ programmer trying to use someone else's code will read the public fields and methods in the header file. They will skip the private variables. This C++ programmer will not know that you intended the private properties to be used in some meaningful way.
Take a look at this sample template on lazy table image loading provided by Apple:
Header
@interface ParseOperation : NSOperation <NSXMLParserDelegate>
{
@private
id <ParseOperationDelegate> delegate;
NSData *dataToParse;
NSMutableArray *workingArray;
AppRecord *workingEntry;
NSMutableString *workingPropertyString;
NSArray *elementsToParse;
BOOL storingCharacterData;
}
Source
@interface ParseOperation ()
@property (nonatomic, assign) id <ParseOperationDelegate> delegate;
@property (nonatomic, retain) NSData *dataToParse;
@property (nonatomic, retain) NSMutableArray *workingArray;
@property (nonatomic, retain) AppRecord *workingEntry;
@property (nonatomic, retain) NSMutableString *workingPropertyString;
@property (nonatomic, retain) NSArray *elementsToParse;
@property (nonatomic, assign) BOOL storingCharacterData;
@end
@implementation ParseOperation
@synthesize delegate, dataToParse, workingArray, workingEntry, workingPropertyString, elementsToParse, storingCharacterData;
Now I know this is not C++ and we shouldn't assume all C++ practices should be honored in Objective C. But Objective C should have good reasons to stray away from general programming practices.
NSMutableArray *workingArray
is used by outside classes. So none of the other ivars should have setters and getters.id delegate
has a setter, the user of this object can switch the delegate in middle of the XML parsing, something that doesn't make sense. Also, NSData *dataToParse
is raw XML data retrieved from the network. Now that it has a setter, the user of this object can corrupt the data.private
in the header? Since all ivars are are synthesized to have getters/setters, they are effectively public. You can set them to anything you want and you can get their value whenever you want.I follow the idiom modeled by this example in many of my classes, so I can try to explain my own justification for this practice.
The properties in this example are declared in a class extension in the .m file. This makes them effectively private. Any attempt to access these properties from another class will cause a "Property not found" error upon compilation.
For developers coming from other languages, it may seem strange to synthesize getters and setters for private instance variables. Indeed, there is only one reason why I do this. When used consistently, synthesized properties can simplify memory management and help avoid careless mistakes that can lead to bugs. Here are a couple of examples:
Consider this:
self.workingPropertyString = [NSMutableString string];
versus this:
workingPropertyString = [[NSMutableString string] retain];
Many developers would claim that these two assignments are functionally equivalent, but there's an important difference. The second assignment leaks memory if workingPropertyString was already pointing at a retained object. To write code functionally equivalent to the synthesized setter, you'd have to do something like this:
NSMutableString *newString = [NSMutableString string];
if (workingPropertyString != newString) {
[workingPropertyString release];
workingPropertyString = [newString retain];
}
This code avoids leaking any existing object that the instance variable may be pointing to, and it safely handles the possibility that you may be re-assigning the same object to the instance variable. The synthesized setter does all of this for you.
Of course we can see that (workingPropertyString != newString)
will always be true in this case, so we could simplify this particular assignment. In fact in most cases you can probably get away with a simple direct assignment to an instance variable, but of course it's the exceptional cases that tend to create the most bugs. I prefer to play it safe and set all my object instance variables through synthesized setters. All my instance object assignments are simple one-liners that look like this:
self.foo = [Foo fooWithTitle:@"The Foo"];
or this:
self.foo = [[[Foo alloc] initWithTitle:@"The Foo"] autorelease];
This simplicity and consistency gives my feeble brain less stuff to think about. As a result I almost never have bugs related to memory management. (I'm aware that the autorelease
idiom could theoretically consume excessive memory in a tight loop, but I have yet to encounter that issue in practice. If I ever do, it's a simple case to optimize.)
One other thing I like about this practice is that my dealloc methods all look like this:
- (void)dealloc {
self.delegate = nil;
self.dataToParse = nil;
self.workingArray = nil;
self.workingEntry = nil;
self.workingPropertyString = nil;
self.elementsToParse = nil;
[super dealloc];
}
EDIT: Daniel Dickison pointed out some risks to using accessors in dealloc that I hadn't considered. See the comments.
where every object property is simply set to nil. This simultaneously releases each retained property while setting it to nil to avoid certain crashes due to EXC_BAD_ACCESS.
Note that I've set self.delegate = nil;
even though that property was declared as (nonatomic, assign)
. This assignment wasn't strictly necessary. In fact, I could do away with properties for my (nonatomic, assign)
objects altogether, but again I've found that applying this idiom consistently across all my instance variables gives my brain less to think about, and further reduces the chance that I'll create a bug through some careless mistake. If necessary I can simply flip a property from (nonatomic, assign)
to (nonatomic, retain)
without having to touch any memory management code. I like that.
One could also use consistency as an argument for synthesizing properties for private scalar variables, as your example has done in the case of BOOL storingCharacterData;
. This practice ensures that every instance variable assignment will look like self.foo = bar;
. I don't usually bother to create private scalar properties myself, but I can see some justification for this practice.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With