views:

248

answers:

4

Okay so im finally to the point where I am testing my iPad App on an actual iPad...

One thing that my app does is display a large (2mb) image in a scroll view. This is causing the iPad to get memory warnings. I run the app in the instruments to check for the leak.

When I load the image, a leak is detected and i see the following in the allocations:

ALl Allocations: 83.9 MB Malloc 48.55 MB: 48.55 MB Malloc 34.63 MB: 34.63 MB

What im trying to understand is how to plug the leak obviously, but also why a 2MB image is causing a malloc of 20x that size

I am very new to programming in obj-c so im sure this is an obvious thing, but I just cant figure it out. Here is the code:

@interface ChartsViewController : UIViewController <UIScrollViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource> {
    IBOutlet UIScrollView *scrollView;
    UIImageView *imageView;
    NSString *chart;
    NSString *chartFile;
    UIPickerView *picker;
    NSDictionary *chartsDictionary;
    NSArray *chartTypes;
    NSArray *charts;
    IBOutlet UILabel *chartNameLabel;
    IBOutlet UIActivityIndicatorView *activityIndicator;



}

@property (nonatomic, retain) UIScrollView *scrollView;
@property (nonatomic, retain) UIImageView *imageView;
@property (nonatomic, retain) NSString *chart;
@property (nonatomic, retain) NSString *chartFile;
@property (nonatomic, retain) IBOutlet UIPickerView *picker;
@property (nonatomic, retain) NSDictionary *chartsDictionary;
@property (nonatomic, retain) NSArray *chartTypes;
@property (nonatomic, retain) NSArray *charts;
@property (nonatomic, retain) IBOutlet UILabel *chartNameLabel;
@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *activityIndicator;


-(IBAction) chartSelected;
- (void)alertView:(UIAlertView *)actionSheet 

///////////////////////////////

-(IBAction) chartSelected {
    [imageView removeFromSuperview];
    imageView = nil;
    chartNameLabel.text = @"";

    NSInteger chartTypeRow = [picker selectedRowInComponent:kChartTypeComponent];
    NSInteger chartRow= [picker selectedRowInComponent:kChartComponent];
    chart = [self.charts objectAtIndex:chartRow];
    chartFile = [chart stringByReplacingOccurrencesOfString:@" " withString:@"_"];


    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                         NSUserDomainMask, YES);


    NSString *docsPath = [paths objectAtIndex:0];
    NSString *tempString = [[NSString alloc]initWithFormat:@"%@/%@.jpg",docsPath,chartFile];




    NSData *temp = [NSData dataWithContentsOfFile:tempString];

    if (temp != NULL){

        temp = nil;
        [imageView removeFromSuperview];
        imageView = nil;

        UIImageView *tempImage = [[UIImageView alloc]initWithImage:[UIImage imageWithContentsOfFile: tempString]];
        [tempString release];
        self.imageView = tempImage;


        scrollView.contentSize = CGSizeMake(imageView.frame.size.width , imageView.frame.size.height);
        scrollView.maximumZoomScale = 4.0;
        scrollView.minimumZoomScale = .05;
        scrollView.clipsToBounds = YES;
        scrollView.delegate = self;
        scrollView.zoomScale = .3;

        [scrollView addSubview:imageView];
        [tempImage release];
        imageView = nil;
        chartNameLabel.text = chart;

    }

    else {

        UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Download Chart" 
                                                       message:@"It appears that you have not yet downloaded this chart. Press OK to download this chart to your iPad. Depending on your internet connection, the download could take several minutes." 
                                                      delegate:self 
                                             cancelButtonTitle:@"OK" 
                                             otherButtonTitles:@"Cancel", nil];
        [alert show];
        [alert release];

    }

    }

- (void)dealloc {

    [imageView release];
    [scrollView release];
    [chartsDictionary release];
    [picker release];
    [chartTypes release];
    [charts release];
    [super dealloc];
}
+2  A: 

Lots of leaks in this code.

For every object created using alloc, you need to release it at some point when you are done using it.

The following items are being leaked and need to be released

  1. tempString
  2. tempImage
  3. alert

Also, you don't need that NSAutoreleasePool, one is created for you by the cocoa framework before the event that calls your IBAction is called and is drained with the method finishes. Also the autorelease pool is also only responsible for items that have been placed in it, which include anything that you send a autorelease message to and any objects you get back from a method other than alloc, new, or one with copy in the title.

Also, know that setting a local variable to nil is not the same as releasing it.

For example the creating the image should be

UIImageView *tempImage = [[UIImageView alloc]initWithImage:[UIImage imageWithContentsOfFile: tempString]];
self.imageView = tempImage;
[tempImage release];

Edit:

One more thing. When you access imageview without using self.imageview you are accessing the ivar directly and not through the property. So when you do self.imageview = tempImage, it retains the image view as it should, but when you do imageview = nil it nils out the reference without releasing the memory. This is another leak. Try self.imageview = nil instead.

As to why it is so much memory, this I do not know unless it is related to expanding the image to its full size (by pixels), and not its compressed jpg size, or to other data being leaked along with the UIImageView object.

Brandon Bodnár
i changed the code and edited my code above, although the problem has not changed.
Brodie
Edited my answer to include another leak.
Brandon Bodnár
A: 

Both of the following lines will retain the UIImageView allocated as tempImage.

self.imageView = tempImage;
[scrollView addSubview:imageView];

add this line after addSubview:

[tempImage release];

You do not need imageView as a member unless you are manipulating it later. If you keep it, be sure to release it in dealloc. In general, every property marked retain should be released in dealloc unless you have some specific reason not to.

I usually do not make properties or even have members for views that will live in the view hierarchy unless I need to manipulate them, for example changing the text of a UILabel or the state of a UIButton.

drawnonward
i edited my code above and also included my dealloc method. the problem has not changed however. It's saying i have 20-30 MBs allocated then each time I load a new chart it adds another 20-30 MB
Brodie
+3  A: 

You say you have a 2MB image. But that means a 2MB JPG, right?

So what is the size in pixels - because when you load the image into memory, it must be de-compressed. And that means it will be horizontal resolution * vertical resolution * 8 * 4 (alpha channel) bytes in memory.

That is why you are seeing 20-30MB allocated every time you load the image, regardless of retain issues (which just mean each 30MB allocated would not be released).

Kendall Helmstetter Gelner
Great explanation,thanks
Brodie
A: 

tempImage is still leaking.

replace the line that says

imageView = nil;

with

[self setImageView: nil];

or

self.imageView = nil;
JeremyP