I try to find out why my app crashes (RSS Reader) if I send a wrong URL to NSXML Parser. I got an EXC_BAD_ACCES
S. So after some Searching I found out that I have to use Zombies. So I added the following arguments to the environment:
CFZombieLevel = 3
NSMallocStaclLogging = YES
NSDeallocateZombies = NO
MallocStackLoggingNoCompact = YES
NSZombieEnabled = YES
NSDebugEnabled = YES
NSAutoreleaseFreedObjectCheckEnabled = YES
I also added malloc_error_break
as breakpoint. Then I added some other breakpoints in the GUI and pressed Build and Debug. In the console I get the following message:
2010-08-28 18:41:49.761 RssReader[2850:207] *** -[XMLParser respondsToSelector:]: message sent to deallocated instance 0x59708e0
Sometimes I also get the following message:
wait_fences: failed to receive reply: 10004003
If I type in "shell malloc_history 2850 0x59708e0" I get the following:
...
ALLOC 0x5970870-0x59709d7 [size=360]: thread_a0aaa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] | -[UIApplication
...
----
FREE 0x5970870-0x59709d7 [size=360]: thread_a0aaa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication
...
ALLOC 0x59708e0-0x597090f [size=48]: thread_a0aaa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoSource1 | __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] | -[UIApplication
...
Binary Images:
0x1000 - 0x6ff3 +RssReader ??? (???) <6EBB16BC-2BCE-CA3E-C76E-F0B078995E2D> /Users/svp/Library/Application Support/iPhone Simulator/4.0.1/Applications/AF4CE7CA-88B6-44D4-92A1-F634DE7B9072/RssReader.app/RssReader
0xe000 - 0x1cfff3 +Foundation 751.32.0 (compatibility 300.0.0) <18F9E1F7-27C6-2B64-5B9D-BAD16EE5227A>
...
What does this mean? How do I know which object 0x59708e0 is? I can't find the code which causes my app to crash. The only thing I know is that it should be a respondsToSelector message. I added a breakpoint to all my respondsToSelector messages. They get hitted but the app crashes not at that point. I also tried to comment them out except for one and also gets the app crashing. The one which was not commented out, wasn't hit. Where do I have a memory leak?
The next confusing thing is that NSXML Parser continue its' work, despite the parseErrorOccurred delegate is called. After two times an error is thrown, the app crashes.
Why is Zombies in the Run with Peformance Tool disabled?
Edit:
Now I used this instruction (not able to post. sorry. spam prevention) I got this working. What is the meaning of this?
@Graham:
In my parser class I instantiate NSXMLParser
:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
...
NSXMLParser *rssParser = [[NSXMLParser alloc] initWithData:responseData];
[rssParser setDelegate:self];
...
[rssParser parse];
//[rssParser release];
}
During I searched the error, I commented out the release method. Currently rssParser never get's released in the parser class.
In my RootViewController
class I instantiate my parser:
- (void)loadData {
if (newsItems == nil) {
[activityIndicator startAnimating];
XMLParser *rssParser = [[XMLPar开发者_开发百科ser alloc] init];
[rssParser parseRssFeed:@"http://feeds2.feedburner.com/TheMdnShowtest" withDelegate:self];
[rssParser release];
rssParser = nil;
} else {
[self.tableView reloadData];
}
}
If I don't release it here, it doesn't crash. But for each alloc I have to do a release? Or should I autorelease NSXMLParser
in connectionDidFinishLoading
?
Zombie is disabled as you use it with Memory Leaks since all Zombies would be signaled as leaks. To run the Zombie tool you can go to the Instrument menu and do File>New and chose the Zombie tool alone, doing so the program will stop if a zombie has received a message and you'll be given a link in a small pop up to that zombie object and its history
Somewhere you're allocating the XMLParser. Let's see that code. You're not auto-releasing it, are you?
Somewhere it's getting released... is it assigned to a property? Let's see that property definition.
Later on the respondsToSelector: method is being invoked, but that could be any method. The point is that your XMLParser got released before you intended.
In RootViewController.h I have declared the property rssParser:
@class XMLParser;
@interface RootViewController : UITableViewController {
...
XMLParser *rssParser;
}
...
@property (retain, nonatomic) XMLParser *rssParser;
@end
In RootViewController.m I have a method called errorOccurred:
- (void)errorOccurred {
[rssParser release];
rssParser = nil;
if ([activityIndicator isAnimating]) {
[activityIndicator stopAnimating];
}
}
In my XMLParser.m file I call the errorOccurred two times:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
...
if ([_delegate respondsToSelector:@selector(errorOccurred)])
[_delegate errorOccurred];
else
{
[NSException raise:NSInternalInconsistencyException
format:@"Delegate doesn't respond to errorOccurred:"];
}
}
To see how _delegate is declared, look at the tutorial http://www.cocoadevblog.com/iphone-tutorial-creating-a-rss-feed-reader. It is an id variable and has a own setter and getter method (you can also declare it as property I think). The second time:
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
...
if ([_delegate respondsToSelector:@selector(errorOccurred)])
[_delegate errorOccurred];
else
{
[NSException raise:NSInternalInconsistencyException
format:@"Delegate doesn't respond to errorOccurred:"];
}
}
The release of my rssParser variables are like the following:
In loadData in RootViewController.m I never release it. Unfortunately it will crash if I do it in loadData. It is only released if a error occurred (see above) or in the dealloc method. But I think that should work fine as it is declared as property.
- (void)loadData {
if (newsItems == nil) {
[activityIndicator startAnimating];
self.rssParser = [[XMLParser alloc] init];
[rssParser parseRssFeed:@"http://www.wrongurl.com/wrongrss.xml" withDelegate:self];
} else {
[self.tableView reloadData];
}
}
In XMLParser.m I release it after the parse method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
...
NSXMLParser *rssParser = [[NSXMLParser alloc] initWithData:responseData];
[rssParser setDelegate:self];
[rssParser parse];
[rssParser release];
rssParser = nil;
}
Note that the two variable names are the same (rssParser), but they are different. In RootViewController I'm creating an instance of XMLParser, and in XMLParser.m I'm creating an instance of NSXMLParser.
So I think I'll leave it at that, until I'm not experiencing a new error or someone of you explain me why this is bad.
Do you also have rssParser, the same instance variable you've set to release here...
- (void)errorOccurred {
[rssParser release];
rssParser = nil;
if ([activityIndicator isAnimating]) {
[activityIndicator stopAnimating];
}
}
...released in your dealloc method? This would cause a double release and thus EXEC_BAD_ACCESS.
精彩评论