views:

179

answers:

2

I'm trying to write a simple RSS reader for the iPhone, and it appeared to be working fine, until I started working with Instruments, and discovered my App is leaking massive amounts of memory.

I'm using the NSXMLParser class to parse an RSS feed. My memory leaks appear to be originating from the overridden delegate methods:

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

and

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

I'm also suspicious of the code that populates the cells from my parsed data, I've included the code from those methods and a few other key ones, any insights would be greatly appreciated.


- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if ([self.currentElement isEqualToString:@"title"]) {
        [self.currentTitle appendString:string];
    } else if ([self.currentElement isEqualToString:@"link"]) {
        [self.currentURL appendString:string];
    } else if ([self.currentElement isEqualToString:@"description"]) {
        [self.currentSummary appendString:string];
    }
}


- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"item"]) {
        //asdf
        NSMutableDictionary *item = [[NSMutableDictionary alloc] init];

        [item setObject:currentTitle forKey:@"title"];
        [item setObject:currentURL forKey:@"URL"];
        [item setObject:currentSummary forKey:@"summary"];

        [self.currentTitle release];
        [self.currentURL release];
        [self.currentSummary release];

        [self.stories addObject:item];
        [item release];
    }
}


// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    // Configure the cell.
    // Set up the cell
    int index = [indexPath indexAtPosition: [indexPath length] - 1];
    CGRect contentRect = CGRectMake(8.0, 4.0, 260, 20);
    UILabel *textLabel = [[UILabel alloc] initWithFrame:contentRect];
    if (self.currentLevel == 0) {
        textLabel.text = [self.categories objectAtIndex: index];
    } else {
        textLabel.text = [[self.stories objectAtIndex: index] objectForKey:@"title"];
    }
    textLabel.textColor = [UIColor blackColor];
    textLabel.font = [UIFont boldSystemFontOfSize:14];
    [[cell contentView] addSubview: textLabel];
    //[cell setText:[[stories objectAtIndex: storyIndex] objectForKey: @"title"]];
    [textLabel autorelease];
    return cell;
}


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {   
    if ([elementName isEqualToString:@"item"]) {
        self.currentTitle = [[NSMutableString alloc] init];
        self.currentURL = [[NSMutableString alloc] init];
        self.currentSummary = [[NSMutableString alloc] init];
    }

    if (currentElement != nil) {
        [self.currentElement release];
    }
    self.currentElement = [elementName copy];
}


- (void)dealloc {   
    [currentElement release];
    [currentTitle release];
    [currentURL release];
    [currentSummary release];
    [currentDate release];

    [stories release];

    [rssParser release];
    [storyTable release];

    [super dealloc];
}


// Override to support row selection in the table view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // Navigation logic may go here -- for example, create and push another view controller.
    // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
    int index = [indexPath indexAtPosition: [indexPath length] - 1];
    if (currentLevel == 1) {
        StoryViewController *storyViewController = [[StoryViewController alloc] initWithURL:[[stories objectAtIndex: index] objectForKey:@"URL"] nibName:@"StoryViewController" bundle:nil];
        [self.navigationController pushViewController:storyViewController animated:YES];
        [storyViewController release];
    } else {
        RootViewController *rvController = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:nil];
        rvController.currentLevel = currentLevel + 1;
        rvController.rssIndex = index;
        [self.navigationController pushViewController:rvController animated:YES];
        [rvController release];
    }
}

A: 

I figured out my problem, all of my memory leaks stemmed from this statement:

self.stories = [[NSMutableArray alloc] init];

This causes the retain count of stories to be incremented by 2, since the setter calls retain on the newly allocated array.

I replaced the above statement with this one and it solved my problem:

NSMutableArray *array = [[NSMutableArray alloc] init];
self.stories = array;
[array release];
Stilton
A: 

Another way of fixing your code is this replacing

self.stories = [NSMutableArray alloc] init];

with

self.stories = [NSMutableArray arrayWithCapacity:10];

The arrayWithCapacity method is autoreleased so you don't need to manually call release. (This is true for other classes i.e. setWithCapacity, stringWithFormat etc)

Thanks, Sam


PS Not helping your question but these lines look a little unusual :

[self.currentTitle release];

You should probably be doing this :

self.currentTitle = nil;

That will release currentTitle the same as your code does but it will also set it to nil which means you can't use it again by mistake!

deanWombourne