Thursday, February 16, 2012

Memory Leak & App Crash when going back to Album List


I'm using three20 to create image viewer. First i'm creating list of albums from the sql db and when user selects any album, its url string is passed to the this code that creates thums of available pics on network using XML Parser. everything works fine but when user goes back to the album list and selects another album. app crashes with 'Thread 1: Program received singal: "EXC+BAD_ACCESS" in main.m . plus XCode Product Analyze gives potential memory leak where i'm creating photoSource in the viewDidLoad. Here is the code




#import "AlbumController.h"
#import "PhotoSource.h"
#import "Photo.h"
#import "AlbumInfo.h"
#import "AlbumDatabase.h"

@implementation AlbumController

@synthesize albumName;
@synthesize urlAddress;

@synthesize images;

- (void)viewDidLoad
{
[super viewDidLoad];
// NSLog(@"%@", self.urlAddress);

[self createPhotos]; // method to set up the photos array
self.photoSource = [[PhotoSource alloc]
initWithType:PhotoSourceNormal
title:self.albumName
photos:images
photos2:nil];


self.navigationController.navigationBar.tintColor = [UIColor blackColor];

}

- (void)viewDidUnload
{
[super viewDidUnload];

// release and set to nil
}

-(void)createPhotos
{
if ([stories count] == 0)
{

NSString *path = self.urlAddress;
[self parseXMLFileAtURL:path];
}


images = [NSMutableArray arrayWithCapacity:[stories count]]; // needs to be mutable

for (int i = 0; i < [stories count]; i++)
{
NSString *img = [[stories objectAtIndex:i] objectForKey:@"image"];
img = [img stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];

//NSString * caption = [[stories objectAtIndex:i] objectForKey:@"caption"];
//caption = [caption stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];

[images addObject:[[[Photo alloc] initWithURL:img smallURL:img size:CGSizeMake(320, 212)] autorelease]];
}
}


#pragma mark -
#pragma mark XML Parser Implementation

- (void)parserDidStartDocument:(NSXMLParser *)parser{
//NSLog(@"found file and started parsing");
}

- (void)parseXMLFileAtURL:(NSString *)URL
{
stories = [[NSMutableArray alloc] init];

//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];

// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];

// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate:self];

// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSString * errorString = [NSString stringWithFormat:@"Unfortunately it is not possible to load Pictures. Please check Internet Connection. (Error code %i )", [parseError code]];
//NSLog(@"error parsing XML: %@", errorString);

UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:@"Failed to load the feed." message:errorString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
//NSLog(@"found this element: %@", elementName);
currentElement = [elementName copy];
if ([elementName isEqualToString:@"item"]) {
// clear out our story item caches...
item = [[NSMutableDictionary alloc] init];
currentCaption = [[NSMutableString alloc] init];
//currentThumbnail = [[NSMutableString alloc] init];
currentImage = [[NSMutableString alloc] init];
}
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
//NSLog(@"ended element: %@", elementName);
if ([elementName isEqualToString:@"item"]) {
// save values to an item, then store that item into the array...

//[item setObject:currentThumbnail forKey:@"thumbnail"];
//[item setObject:currentCaption forKey:@"caption"];
[item setObject:currentImage forKey:@"image"];

[stories addObject:[[item copy] autorelease]];
}
}

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

// save the characters for the current item...
if ([currentElement isEqualToString:@"thumbnail"]) {
//[currentThumbnail appendString:string];
}// else if ([currentElement isEqualToString:@"caption"]) {
//[currentCaption appendString:string];
//}
else if ([currentElement isEqualToString:@"image"]) {
[currentImage appendString:string];
}
}

- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog(@"all done!");
NSLog(@"stories array has %d items", [stories count]);
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
return YES;
}
else
{
return NO;
}

}



#pragma mark -
#pragma mark Memory Management

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}

- (void)dealloc {

[currentElement release];
[rssParser release];
[stories release];
[item release];
[currentCaption release];
//[currentThumbnail release];
[currentImage release];
[images release];
[stories release];

[super dealloc];
}

@end



and here is the didSelectRowAtIndexPath thats pushing this view




- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
AlbumInfo *info = [_albumInfos objectAtIndex:indexPath.row];
AlbumController *albumController = [[AlbumController alloc] init];

albumController.urlAddress = info.address;
albumController.albumName = info.name;
[self.navigationController pushViewController:albumController animated:YES];
[albumController release];
}



Here is the code for AlbumController.h




#import <Foundation/Foundation.h>
#import "Three20/Three20.h"

@interface AlbumController : TTThumbsViewController <NSXMLParserDelegate>
{
NSString *albumName;
NSString *urlAddress;

// images
NSMutableArray *images;

// parser
NSXMLParser * rssParser;
NSMutableArray * stories;
NSMutableDictionary * item;
NSString * currentElement;
NSMutableString * currentImage;
NSMutableString * currentCaption;

}

@property (nonatomic, strong) NSString *albumName;
@property (nonatomic, strong) NSString *urlAddress;

@property (nonatomic, retain) NSMutableArray *images;

- (void)createPhotos;
- (void)parseXMLFileAtURL:(NSString *)URL;

@end



used this tutorial http://www.raywenderlich.com/1430/how-to-use-the-three20-photo-viewer



Need help solving this memory leak and need to know why its crashing.



Thanks

2 comments:

  1. Simple. In Xcode 4.0+, Just click-hold on the Run icon, and press Profile. It'll open up Instruments, and you'll want Zombies. Then navigate your app to where the crash happened before, and this time, it'll show up in Instruments with the caller, and all the information about it.

    ReplyDelete
  2. The memory leak in viewDidLoad is caused by the following line:

    self.photoSource = [[PhotoSource alloc]
    initWithType:PhotoSourceNormal
    title:self.albumName
    photos:images
    photos2:nil];


    [PhotoSource alloc] returns an object you own (with a retain count of +1).
    initWithType:title:photos:photos2: does not change the retain count.

    So viewDidLoad is left with an object it owns, but no pointer to it. To balance the alloc you should send an autorelease message:

    self.photoSource = [[[PhotoSource alloc]
    initWithType:PhotoSourceNormal
    title:self.albumName
    photos:images
    photos2:nil] autorelease];

    ReplyDelete