views:

670

answers:

3

I have a problem with a photo viewing application I've written.

I have a UITabBarController with a UINavigationController inside. The UINavigationController initially displays a UITableView. On selection it pushes another UIViewController (Individual photo album) along with an NSArray of photos. This Controller contains a UIScrollView/UIPageControl which displays several UIViewControllers in which there is a UIImageView.

The application initially works great. It loads each image correctly for each Album and you can go back from the navigation bar. The problem is after about 180 images the app starts to throw memory warnings and eventually crashes throwing "Program received signal: “0”. warning: check_safe_call: could not restore current frame" which I believe is to do with low memory. It's incredibly frustrating because I've checked and there no leaks and every dealloc is being called as it should. The dealloc methods release every retained property and set them to nil.

If you check in instruments it shows the memory usage gradually going up after every album has been viewed. It does release some memory but not all of it. e.g. if the album uses 1MB to display 0.9MB might be released.

Any help would be appreciated. This is the last issue before I release it.

EDIT: This is a link to the basic project files. http://www.mediafire.com/?nztrd1yhzoo

AlbumsViewController (pushes an individual "albumviewcontroller")

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

 NSMutableDictionary *dictThisItem = [self.arrAlbums objectAtIndex:[indexPath row]];
    NSString *strBand = [dictThisItem objectForKey:@"album"];

  NSMutableArray *arrThesePhotos = [[self.arrAlbums objectAtIndex:[indexPath row]] objectForKey:@"photos"];

  if (self.albumViewController == nil){
   self.albumViewController = [[AlbumViewController alloc] initWithNibName:nil bundle:nil];
  }
  albumViewController.hidesBottomBarWhenPushed = YES;
  [self.navigationController pushViewController:albumViewController animated:YES];
  self.albumViewController.arrPhotos = arrThesePhotos;
  [albumViewController populateScroller];
}

AlbumViewController

- (void)populateScroller {

imagesScroller.pagingEnabled = YES;
imagesScroller.contentSize = CGSizeMake(imagesScroller.frame.size.width * [self.arrPhotos count], 380);
imagesScroller.showsHorizontalScrollIndicator = NO;
imagesScroller.showsVerticalScrollIndicator = NO;
imagesScroller.scrollsToTop = NO;
imagesScroller.delegate = self;
imagesScroller.backgroundColor = [UIColor blackColor];
[imagesScroller scrollRectToVisible:CGRectMake(0.0, 0.0, 320.0, 480.0) animated:NO];

pageControl.numberOfPages = [self.arrPhotos count];
pageControl.currentPage = 0;
 pageControl.backgroundColor = [UIColor blackColor];



 NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.arrPhotos count]; i++) {

  CGRect frame = imagesScroller.frame;
    frame.origin.x = frame.size.width * i;
    frame.origin.y = 0;

  NSString *strImagePath = [[self.arrPhotos objectAtIndex:i] stringByReplacingOccurrencesOfString:@"iPhone" withString:@"iPhone_thumbnail"];

  ImageViewController *imageViewController = [ImageViewController alloc];
  imageViewController.localImage = YES;
  imageViewController.albumViewController = self;
  [imageViewController initWithPhotoName:strImagePath];
  [controllers addObject:imageViewController];

  imageViewController.view.frame = frame;
  [imagesScroller addSubview:imageViewController.view];

  [imageViewController release];

}
self.viewControllers = controllers;
[controllers release];

}

ImageViewController

- (void)viewDidLoad {

 self.navigationShown = NO;

 Cache *cache = [[Cache alloc] init];
 [cache release];


 NSString *strURL = [@"http://www.marklatham.co.uk" stringByAppendingString:self.strThisPhoto];
 NSString *strTmpPrefix = (self.localImage) ? @"_tmp_rockphotothumb_" : @"_tmp_rockphotolarge_";

// Cache Paths
NSArray *arrPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *strLocalPath = [[arrPaths objectAtIndex:0] stringByAppendingString:@"/"];
NSString *strPrefix = (strTmpPrefix != nil) ? strTmpPrefix : @"_tmp_rockphotolarge_";

NSMutableArray *arrImagePaths = (NSMutableArray *)[strURL componentsSeparatedByString:@"/"];


// Check cache
NSString *strEntireLocalCache = [strLocalPath stringByAppendingString:[strPrefix stringByAppendingString:[arrImagePaths objectAtIndex:[arrImagePaths count]-1]]];
if ([[NSFileManager defaultManager] fileExistsAtPath:strEntireLocalCache]){

    UIImageView *imvImageView = [UIImageView alloc];
    UIImage *image = [[UIImage imageWithContentsOfFile:strEntireLocalCache] autorelease]; 
    [imvImageView initWithImage:image];

    CGSize imgSize = image.size;
    CGFloat fltWidth = imgSize.width;
    CGFloat fltHeight = imgSize.height;

    // If landscape rotate image
    if (fltWidth > fltHeight){
        imvImageView.frame = CGRectMake(-80.0, 80.0, 481.0, 320.0);

        CGAffineTransform rotate = CGAffineTransformMakeRotation(-1.57079633);
        [imvImageView setTransform:rotate];
    }else{
        imvImageView.frame = CGRectMake(0.0, 0.0, 320.0, 481.0);
    }

    [self.view addSubview:imvImageView];
    [imvImageView release];

}else{

    // Data URL Downloading
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    NSData *datImageData = [NSData dataWithContentsOfURL: [NSURL URLWithString:strURL]];
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

    [datImageData writeToFile:strEntireLocalCache atomically:YES];

    UIImageView *imvImageView = [UIImageView alloc];
    UIImage *image = [[UIImage imageWithData: datImageData] autorelease];
    [imvImageView initWithImage:image];

    CGSize imgSize = image.size;
    CGFloat fltWidth = imgSize.width;
    CGFloat fltHeight = imgSize.height;

    // If landscape rotate image
    if (fltWidth > fltHeight){
        imvImageView.frame = CGRectMake(-80.0, 80.0, 481.0, 320.0);

        CGAffineTransform rotate = CGAffineTransformMakeRotation(-1.57079633);
        [imvImageView setTransform:rotate];
    }else{
        imvImageView.frame = CGRectMake(0.0, 0.0, 320.0, 481.0);
    }

    [self.view addSubview:imvImageView];
    [imvImageView release];

}

}
A: 

Using the ObjectAlloc Instrument properly, you can find out exactly where the memory being used and therefore find the cause of the memory leak.

Ken Aspeslagh
I was just assuming you were leaking since you said it was throwing memory warnings. You wouldn't get memory warnings unless you were running out of memory, and the only way to run out of memory over time is to be leaking memory. ;)Use ObjectAlloc and use the "Created and still living" option to find out what that 0.1 MB that isn't being released is.
Ken Aspeslagh
ObjectAlloc can often hide the true memory usage for display elements. For a more accurate readout of overall memory usage, you need to turn to the Memory Monitor instrument.
Brad Larson
According to the leak tool there are no leaks and if you add an NSLog to dealloc in ImageViewController it shows it's being called after the image if added to the UIScrollView (and presumably the controllers array). What exactly does "Real Memory" show in the Memory Monitor tool? Is it active memory? If this is the case I'm completely confused as it shows the value going up after each image is loaded but hardly any memory is released when dealloc is called in "ImageViewController". Would you mind taking a look at the project (see post) to see if you can see anything obvious.
Lee M
A: 

Do use Instruments and check the leaks view to be sure.

But if you're sure it isn't leaking: If you load fewer images, does this problem take longer to occur? You might want to try downsampling the images in order to fit more in memory, or loading them from disk only as needed.

Also, on this line:

UIImage *image = [[UIImage imageWithContentsOfFile:strEntireLocalCache] autorelease];

I believe the autorelease call is unnecessary, because the method name idiom +objectWithArgument: returns an autoreleased object. I'm not sure whether autoreleasing twice can have bad consequences later on, but it's worth a shot. Try taking that out and see if anything changes.

Kevin Conner
According the leak tool there are no leaks. The number of images in each album varies. If you load less of them it takes longer to crash. The images have already been downsampled as far as we can take them. I thought this was the case but as I was going through the code ensuring all objects were released I added autorelease anyway.
Lee M
A: 

After convincing my client I managed to get around this by going with the three20 framework.

Lee M