DAN
DAN

Reputation: 939

Updating UIImageView takes too much memory

I have a code block which is executed about every 20ms (it is a rendering callback method for a an audio unit of Core Audio); such method runs some calculations and updates the image of an UIImageView. In order to force the UI to be refreshed, I schedule the setter for the image of the UIImageView in the main thread, as follows:

[audioUnit setOutputBlock:^() {
    // ...
    // some heavy calculations
    // ...
    dispatch_sync(dispatch_get_main_queue(), ^{    
        imageView.image = images[index];
    });        
}];

Without the setter of the image, the app takes about 50mb of memory, whereas the refresh of the image makes it jump to about 300mb, resulting in a memory warning and crash on iPhone < 5.

What should be the best approach to face such issue?

Thanks a lot, DAN

Upvotes: 2

Views: 799

Answers (1)

ipmcc
ipmcc

Reputation: 29916

Your data set is rather large here. Think about it after the images are decompressed: 1136 height x 640 width x 4 bytes per pixel x 110 images = 320MB. Unfortunately, image decompression is not fast/free, so if you want this to happen at 50Hz, you're going to have to play some tricks. For starters, you probably won't be able to keep all 110 images in memory at the same time, so stop trying to do that.

Also, don't use -[UIImage imageNamed:]. It aggressively caches decompressed versions of your image in a way that you have no control over. I would user [[UIImage alloc] initWithContentsOfFile:] so as to have maximum control over the lifecycle of your images.

If the decompression time is tolerable, then it may be as simple as keeping an array of paths to the images and then making the one UIImage object you need at the last possible minute. I suspect that 20ms is going to be a little too slow to do the decompression each time though.

Another option would be to pre-decompress the images, and then write the decompressed data to the application's cache directory on disk. This way, you're still loading from disk but it's a straight read and not a read-and-decompress every time. Note that this will necessarily consume ~320MB of disk storage. Not too horrible.

To take that one step further, with a few extra shenanigans, you might be able to get it so the images are on disk, and then the image objects in your application could point to mmap'ed memory regions corresponding to each file on disk. This way you let the virtual memory system decide what's important to have in memory and what's not, and you get that "for free." That seems like probably the best shot for this. (EDIT: I looked and it appears that UIImage already mmaps the images behind the scenes, so this suggestion might not be much help to you.)

Just one thing I noticed from the snippet you posted: You are capturing the whole array in the block closure, which means that every image in the array stays alive for the duration of that block (and some period of time longer, until libdispatch decides to release the block.) It probably won't be enough to avoid this (hence my recommendations above) but it certainly isn't helping to capture the whole array. Here's what I mean:

[audioUnit setOutputBlock:^() {
    @autoreleasepool {

        NSArray* images = [NSArray array]; // or whatever
        // ...
        // some heavy calculations
        // ...
        UIImage* theOneImageWeNeed = images[index];
    }
    dispatch_sync(dispatch_get_main_queue(), ^{    
        imageView.image = theOneImageWeNeed;
    });        
}];

That will keep the one image you need alive, and more aggressively release any other objects generated by the calculations.

Upvotes: 7

Related Questions