I recently (after hours of debugging) tracked down a segfault in an Objective C iPad app. To boil it down, I had an object TOP which owned MIDDLE which owned BOTTOM. MIDDLE and BOTTOM had retain counts of 1. MIDDLE passed BOTTOM to a method in TOP which ended up releasing MIDDLE, thereby causing BOTTOM to be released and dealloc'd. When the same method in TOP continued working with BOTTOM, it segfaulted. (Note that there are multiple layers of indirection that I left out of the description to keep it simple, but which made debugging a chore.)
Is there a name for what happened? Is there a pattern I can follow to prevent it from happening in the future? Why doesn't the runtime retain objects on the callstack by essentially wrapping methods with [self retain]
and [self release]
(or retaining arguments the same way, or both)?
Edit:
To be clear, when TOP releases MIDDLE, it sets the pointer to nil. MIDDLE is never accessed through an invalid pointer.
Edit 2: I should have posted actual code to begin with. This is essentially what I have:
// also known as TOP
@interface MyAppDelegate : NSObject <UIApplicationDelegate> {
UIViewController* controller;
}
@end
@implementation MyAppDelegate
- (void)displayDoc:(Document*)doc {
DocController* c = [[DocController alloc] initWithNibName:@"DocController" bundle:nil doc:doc];
[controller release];
// controller was previously an instance of HomeController
controller = c;
}
- (void)displayBookmark:(Bookmark*)bookmark {
[self displayDoc:bookmark.document];
[controller setPage:bookmark.page];
}
- (void)dealloc {
[controller release];
[super dealloc]
}
@end
// also known as MIDDLE
@interface HomeController : UIViewController {
}
@end
@implementation HomeController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Bookmark* b = ...; // pull out of existing data structure, not created here
MyAppDelegate* app = ...;
[app displayBookmark:b];
}
@end
// also known as BOTTOM
@interface Bookmark : NSObject {
}
@property NSString* name;
// etc.
@end