views:

116

answers:

3

Hi,

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_ACCESS. 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?

I hope someone can help.

Edit:

Now I used this instruction (not able to post. sorry. spam prevention) I got this working. Here is the result: http://yfrog.com/mrzombievp So 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 = [[XMLParser 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?

Cheers

A: 

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

rano
Thanks for the hint! I realized this when reading http://www.corbinstreehouse.com/blog/2007/10/instruments-on-leopard-how-to-debug-those-random-crashes-in-your-cocoa-app/I didn't knew that Instruments is a standalone program and not part of Xcode. Nevertheless I don't understand why they are offering that menu entry, if it couldn't be used.
testing
A: 

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.

Graham Perks
So I edited my question. I'm using alloc, so I don't think I'm autoreleasing it. I don't use the RSS Parser as a property. When it is the right time to release it?
testing
Looks like your XMLParser class is expecting some async I/O response? If so, it needs to hang around until complete. Therefore you can't release it in loadData. Autoreleasing itself in connectionDidFinishLoading: would be worth trying, if that's when it completes processing. You'd have to do the same in any connection error handling method, too.
Graham Perks
I didn't looked through the synchronous/asynchronous stuff. So I'm telling you what I use. I use NSURLConnection and NSXMLParser. NSURLConnection is creating asynchronous connections and the NSXMLParser is a SAX Parser. Since I'm starting the parsing in connectionDidFinishLoading: the parsing takes place, when the file was downloaded (the faulty file is a HTML file, where a redirection takes place after 5 seconds). The project is based on this tutorial: http://www.cocoadevblog.com/iphone-tutorial-creating-a-rss-feed-reader.
testing
A: 

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.

testing