views:

41

answers:

2

I have an array of UIImages that contains some .jpg images downloaded from the net when the app starts. I also have a table view that shows some of these images in its cells. The problem is that when I scroll the table, the app's memory consumption always increases up to the point where the app crashes due to low memory conditions. The table cells seem to be reused fine, so my theory is the following.

Since the UIImageView in a table cell only retains one of the elements in the image array, when the cell gets reused, and a new image is assigned to it, the previously used image is of course not destructed (the cell releases it, but the array still retains). However, the decompression cache used to hold the raw image data (computed the first time the UIImage is displayed by a view) belongs to the UIImage itself, so it also remains. But I'm just guessing about all this.

Can this really be the problem? If so, how can I work around it? My first idea was to create a copy of the UIImage whenever it is assigned to a cell, but looks like UIImages can't be deep copied. Is there any way to tell a UIImage to keep the compressed jpg data in memory but throw away the decompression cache? (In the worst case I guess I can still download all the images and store them in local files, then load them from there and completely release the UIImages when not displayed anymore, but this doesn't seem to be an elegant solution.)

--

I can't post the original code (as suggested in comments) as it is fairly complicated with custom table cells and custom views and even a background thread downloading the images, but I've just created a small test app and it seems to show the same behavior. Here's a little function that is called when the user taps a UIButton:

- (IBAction)onNext:(UIButton*)sender
{
    static NSMutableArray* images = nil; 
    if (!images)
    {
        NSArray* names = [NSArray arrayWithObjects:
            @"IMG_2957.JPG", 
            @"IMG_2962.JPG", 
            @"IMG_2965.JPG", 
            @"IMG_2970.JPG", 
            @"IMG_2971.JPG", 
            @"IMG_2978.JPG", 
            nil];
        images = [NSMutableArray new];
        for (int i = 0; i < names.count; ++i)
            [images addObject:[UIImage imageNamed:[names objectAtIndex:i]]];
        int i = 42;
    }
    static int current = 0;

    imageView.image = [images objectAtIndex:current];
    ++current; 
}

Yes, I know that the images array is leaking, that's not the point (in the original code I also want to retain the images for the entire lifetime of the app, and only release on quitting). But. According to Memory Monitor, after the first tap (all images get loaded, and the first one is displayed), the app consumes ~5MB (all jpgs loaded, one of them decompressed). After all subsequent taps, the memory consumption increases by ~2MBs (yep, I tested with 1024x768 images). So it looks like decompressed data of the previously displayed images is still there. I know this wouldn't be a problem if I released the image once it isn't visible anymore, and that would also release the decompressed buffer, but if possible, I'd like to retain all the images, and only release the decompressed data. Is that possible?

+1  A: 

I don't think UIImage has no memory leaks that fatal. I have been working on few applications which has to deallocate UIImage a lot due to memory constraint, but it's still working fine. I believe your image leaks somewhere. Show us some code so someone will point that out for you.

tia
Technically, it's not a leak; if I released the UIImages, I'm sure the decompressed buffers would be released fine. However, I'd like to retain the images, I would just like to release the decompressed data. I've edited the question to show some code.
imre
I don't think you can do that. I also don't think UIImage will keep compressed data internally. If you really want to cache the data, I would suggest using NSData to do so.
tia
+1  A: 

If you're reusing cells, make sure those cells implement:

-(void)prepareForReuse
{
      // releasing the image
      self.imageView = nil; // Or release depending on you memory strategy
      self.labelView = nil; // get them all
}
NWCoder