I was working on a Cocoa Touch application that involves XML parsing using NSXMLParser. The source of the XML file is from a URL so a thread is used to do the background networking and parsing stuff, wrapped inside an autoreleasing pool. When running the application, I got an EXC_BAD_ACCESS error when the autorelease pool was released. As Rusty’s wonderful little blog points out, such an error usually comes from releasing an object that you did not create (does not belong to you). I went back to checking every line of code making sure I wasn’t releasing anything of the case, and still came up empty. In the end, I went back to the sample code in SeismicXML and caught a tiny thing which turns out to be the culprit. See if you can spot the problem.
Code calling to the XML parser:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
XMLParser *sc = [[XMLParser alloc] init];
NSError *error = [[NSError alloc] init];
NSURL *url = ....;
[sc parseXMLFileAtURL:url searchError:&error];
...
[error release];
[sc release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[pool release];//EXC_BAD_ACCESS
Implementation of the XMLParser’s parse method:
- (void)parseXMLFileAtURL:(NSURL *)URL parseError:(NSError **)error
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:URL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[parser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
NSError *parseError = [parser parserError];
if (parseError && error) {
*error = parseError;
}
[parser release];
}
Found it??
The problem is with NSError *error. Even though there’s an [[NSError alloc] init] paired with a [error release], when the pointer to the pointer of this error object is passed to the parseXMLFileAtURL:parseError: method, the allocated error object is actually dropped from being referenced by the pointer and the pointer to the pointer is changed to pointing to [parser parseError], which returns the error object created (and autoreleased) somewhere inside the parser and does not belong to the caller of the parseXMLFileAtURL:parseError: method. Once returned into the caller, this error object is released by mistake. When the autorelease pool is released, it sends a release to the already released error object, causing the bad access error. So by incorrectly allocating an NSError object to the caller before calling a method that uses pointer of pointer to pass back an object in addition to the returned object, and releasing it later inside the caller, a bad access problem as well as a memory leak are created at the same time. Brilliant!
The Fix? Change these 2 lines in the caller code and you are good to go.
NSError *error = nil;
....
[error release];
This is the kind of memory problem that can be disguised despite matching alloc-releases.