In my last post, I mentioned that I would run Fort Collins for Kids through some memory analysis tools. Apple provides a number of these, including Analyze, a static code analyzer, and Instruments, a runtime profiling tool. I used both Analyze and the Allocations module of Instruments and went hunting. There were a couple of memory leaks related to the delegates I talked about last time. I’ll focus on one of these today.
If you recall, I created an EditExistingAttractionDetailViewDelegate class to edit existing Attractions:
@interface EditExistingAttractionDetailViewDelegate : NSObject <AttractionDetailViewDelegate>
@property (nonatomic, retain) Attraction *existingAttraction;
@end
@implementation EditExistingAttractionDetailViewDelegate
@synthesize existingAttraction = _existingAttraction;
- (void)save:(Attraction *)newAttraction
{
[[AttractionStore defaultStore] replaceAttraction:self.existingAttraction WithAttraction:newAttraction];
}
- (void)dealloc
{
[_existingAttraction release];
[super dealloc];
}
@end
The list view controller creates this object when the user clicks on an item in the list (tableView:didSelectRowAtIndexPath). The issue is that tableView:didSelectRowAtIndexPath adds the AttractionDetailViewController to our UINavigationController and then exits. I needed a way to keep the EditExistingAttractionDetailViewDelegate around, so I created a member variable to store it.
So far, so good. But note that the EditExistingAttractionDetailViewDelegate stores an Attraction property. When does that member variable get released? Why, inside the dealloc method of EditExistingAttractionDetailViewDelegate. And when does that get called? Why, when the program ends. Hmmmm…. The way I noticed this is that when I deleted all the items in the list, one Attraction object remained behind. As leaks go, that’s not bad- it only leaks one instance of Attraction no matter how long the program runs.
Not bad, but not good enough. I fixed it by clearing out the Attraction member variable when I’m finished with it. And when am I finished with it? When the user saves or cancels the editing. It looks like I found a use for the optional cancel method after all!
- (void)save:(Attraction *)newAttraction
{
[[AttractionStore defaultStore] replaceAttraction:self.existingAttraction WithAttraction:newAttraction];
//We can throw out our existing attraction now. Note that if we don't do this, it would get released when this
//delegate object gets released. But it would stick around until then, and we have no reason to do so.
[_existingAttraction release];
_existingAttraction = nil;
}
- (void)cancel
{
//We can throw out our existing attraction now. Note that if we don't do this, it would get released when this
//delegate object gets released. But it would stick around until then, and we have no reason to do so.
[_existingAttraction release];
_existingAttraction = nil;
}
I ran into one glitch when getting this to run. My original code in the cancel method of AttractionDetailViewController was as follows:
if ([self.delegate respondsToSelector:@selector(cancel:)]) {
[self.delegate cancel];
}
The books and examples are all insistent that you need a colon after cancel in the call to respondsToSelector. And that’s because every example includes an input parameter which means that the colon is part of the name. In this case, there are no parameters to cancel, which makes the official method name cancel rather than cancel:. This set me back for the better part of a morning.
With a number of other changes, the code is clean and ready for the next step- adding data persistance.