I just got Aaron Hillegass’ 3rd edition of Cocoa(R) Programming for Mac(R) OS X (3rd Edition) and am working through it as if I never went through the 2nd edition. (It’s been so long since I did any Cocoa, that seemed like the smart thing to do.) Anyway, I’m in Chapter 4 on memory management and things were going fine. Until all of a sudden, the program started dying with a segmentation fault. After trying to figure out what I’d done wrong, I figured I’d mistyped something and just wasn’t seeing it, so I reverted my changes back to what I had in my Subversion repo. I then started making the changes again. And again the program died with a segfault.
Any time I’m working through a programming book, I always type all the examples in by hand, rather than downloading pre-written code. I learn better that way. But in cases like this, I don’t have a problem with looking at the author-provided code to see where I went wrong. I downloaded the solutions from Aaron’s site and started comparing. I found the problem pretty quickly.
Originally, the code for creating the NSCalendarDate object for today’s date was
NSCalendarDate *now = [[NSCalendarDate alloc] init];
which, when you later add the code to release it, would have worked fine. But at some point between when I wrote that line of code and when I added the release, Aaron mentioned a convenience method on NSCalendarDate for getting back an autoreleased date object. That code looks like this
NSCalendarDate *now = [NSCalendarDate calendarDate];
I had changed my code to use that instead of the original version. That was in Chapter 3. So, when I hit Chapter 4 and added the call to release the date object, I ended up with my segfault. The chapter assumed that you still had the alloc & init calls, and so made no mention of the calamity that would ensue if you had switched to the other way of getting today’s date.
What made this difficult to find was when the segfault occurred. It didn’t happen when I called release on the date object. It happened on this line
which is one line before the program ends. That’s boilerplate code there, so it was really strange that it was barfing. The reason this caused the problem is that this way of getting a date is autoreleased, which means that unless I retain it somewhere, it’s going to get deallocated when the NSAutoReleasePool is deallocated. But when I called release on it, I ended up setting it’s retain count to 0, so when the pool was drained, it ended up sending a release message to an object that had already been deallocated, which is a no-no.
Bottom line is that had this been a big program, this could have been really hard to track down. Of course, turning on the Objective-C 2.0 garbage collector would have solved the problem too, but then you have a Leopard-only program.