February 17, 2009

Property Pitfalls and Patterns

Properties are a handy, if not a cumbersome, addition to Objective-C 2.0. Although they add a good deal of convenience, you must never forget what is happening under the covers, else you could end up with problems and confusion. The problem I blindly walked into was not considering the automatic retain done when using the setter. Consider this statement:
self.name = input_name;
This actually invokes a setter that the compiler has synthesized for you. If you declared the property with the "retain" attribute, as in this:
@property (nonatomic,retain) NSString* name;
then what the above assignment will do is:
  1. release whatever name is pointing to
  2. set the new value
  3. retain the new value (take ownership)
This shortcut is extremely handy; however, it's easy to forget that there are some cases where you are already the owner of an object, so the retain is redundant and will cause a memory leak:
self.name = [[NSString alloc] initWithString:input_name];
When you alloc an object, the retain count is already set to one. When you assign this to your property, the setter will bump up the retain count again. The retain count of self.name is now 2. Thus, you have a memory leak. [update date:@"Feb 19, 2009"]; It should be noted that the line above is different than this line:
name = [[NSString alloc] initWithString:input_name];
This form is accessing the instance variable directly, whereas the self.name form is invoking the property's setName: method. This distinction is important because when you skirt around the setter, you don't get the 3 little "side-effects" mentioned above. This can be both useful and dangerous. I recommend NOT using the instance variable directly if it is a property, unless you are in an initializer. It is just too easy to get things wrong; if you assign directly to the instance variable and it already has something assigned to it, you get a memory leak. It is best to be consistent with how you use your properties. [endupdate]; There are a couple solutions I have seen so far, but I don't think any of them are very pretty. One common trick is to autorelease the return from init. This way, the retain count will still be 2, but the next time the autorelease pool is emptied, the redundant reference is cleaned up:
self.name = [[[NSString alloc] initWithString:input_name] autorelease];
This violates the general rule of avoiding autorelease as much as possible. Autoreleasing will release the memory (at some point), but you have no control when that actually happens. Memory could get bloated or even run out before it does happen. When coding on a limited resource device like the iPhone, I think this is a good rule to follow. Another solution is simply to release after the assignment:
self.name = [[NSString alloc] initWithString:input_name];
[self.name release];
But this is non intuitive and can be confusing. A clumsier but cleaner way would be to use a local variable:
NSString* temp = [[NSString alloc] initWithString:input_name];
self.name = temp;
[temp release];
Any other ideas on how best to do this? There is another pattern in common use that I missed in my first applications. It concerns dealloc and properties. Here is what I used to do:
- (void) dealloc
{
  [property1 release];
  [property2 release];

  [super dealloc];
}
Seems perfectly reasonable, and I believe in most cases, this is fine. However, the preferred mechanism for releasing properties is to assign nil to them:
- (void) dealloc
{
   self.property1 = nil;
   self.property2 = nil;

   [super dealloc];
}
So what's the difference? I'm still searching for the definitive answer, but I believe this is preferred because it releases the property and sets it to nil. How? At first glance, this looks like a memory leak. But remember the steps that synthesized setters do for you: 1) release the old value, 2) assign the new value, then 3) retain the new value. In this case, you can't retain nil, so the 3rd step is a no-op. This is a common technique in C++ destructors, which prevents a deallocated pointer from being used later on. This syntax is not intuitive, though, and you have to understand what's going on in the setter.

2 comments:

  1. Static constructor methods have a different retain policy than [[alloc init]. And they are single step! Setting reference properties to nil is a very recognizable pattern for smart pointers (like properties).

    ReplyDelete
  2. That's a good point. The so-called "convenience constructors" do NOT automatically set the retain count to 1, so assigning these to a property does the right thing. But I don't believe all Cocoa classes support these.

    I'm not so sure that setting properties to nil is a common practice. MANY examples in the Apple docs and iPhone books do NOT do it that way.

    ReplyDelete