Dan Ray
Dan Ray

Reputation: 21903

Memory Troubles with UIImagePicker

I'm building an app that has several different sections to it, all of which are pretty image-heavy. It ties in with my client's website and they're a "high-design" type outfit.

One piece of the app is images uploaded from the camera or the library, and a tableview that shows a grid of thumbnails. Pretty reliably, when I'm dealing with the camera version of UIImagePickerControl, I get hit for low memory. If I bounce around that part of the app for a while, I occasionally and non-repeatably crash with "status:10 (SIGBUS)" in the debugger.

On low memory warning, my root view controller for that aspect of the app goes to my data management singleton, cruises through the arrays of cached data, and kills the biggest piece, the image associated with each entry. Thusly:

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Low Memory Warning"
                                                    message:@"Cleaning out events data"
                                                   delegate:nil
                                          cancelButtonTitle:@"All right then."
                                          otherButtonTitles:nil];
    [alert show];
    [alert release];

    NSInteger spaceSaved;

    DataManager *data = [DataManager sharedDataManager];
    for (Event *event in data.eventList) {
        spaceSaved += [(NSData *)UIImagePNGRepresentation(event.image) length];
        event.image = nil;
        spaceSaved -= [(NSData *)UIImagePNGRepresentation(event.image) length];
    }

    NSString *titleString = [NSString stringWithFormat:@"Saved %d on event images", spaceSaved];

    for (WondrMark *mark in data.wondrMarks) {
        spaceSaved += [(NSData *)UIImagePNGRepresentation(mark.image) length];
        mark.image = nil;
        spaceSaved -= [(NSData *)UIImagePNGRepresentation(mark.image) length];
    }

    NSString *messageString = [NSString stringWithFormat:@"And total %d on event and mark images", spaceSaved];

    NSLog(@"%@ - %@", titleString, messageString);

    // Relinquish ownership any cached data, images, etc that aren't in use.
}

As you can see, I'm making a (poor) attempt to eyeball the memory space I'm freeing up. I know it's not telling me about the actual memory footprint of the UIImages themselves, but it gives me SOME numbers at least, so I can see that SOMETHING'S happening. (Sorry for the hamfisted way I build that NSLog message too--I was going to fire another UIAlertView, but realized it'd be more useful to log it.)

Pretty reliably, after toodling around in the image portion of the app for a while, I'll pull up the camera interface and get the low memory UIAlertView like three or four times in quick succession. Here's the NSLog output from the last time I saw it:

2010-05-27 08:55:02.659 EverWondr[7974:207] Saved 109591 on event images - And total 1419756 on event and mark images
wait_fences: failed to receive reply: 10004003
2010-05-27 08:55:08.759 EverWondr[7974:207] Saved 4 on event images - And total 392695 on event and mark images
2010-05-27 08:55:14.865 EverWondr[7974:207] Saved 4 on event images - And total 873419 on event and mark images
2010-05-27 08:55:14.969 EverWondr[7974:207] Saved 4 on event images - And total 4 on event and mark images
2010-05-27 08:55:15.064 EverWondr[7974:207] Saved 4 on event images - And total 4 on event and mark images

And then pretty soon after that we get our SIGBUS exit. So that's the situation. Now my specific questions:

THE time I see this happening is when the UIPickerView's camera iris shuts. I click the button to take the picture, it does the "click" animation, and Instruments shows my memory footprint going from about 10mb to about 25mb, and sitting there until the image is delivered to my UIViewController, where usage drops back to 10 or 11mb again. If we make it through that without a memory warning, we're golden, but most likely we don't. Anything I can do to make that not be so expensive?

Second, I have NSZombies enabled. Am I understanding correctly that that's actually preventing memory from being freed? Am I subjecting my app to an unfair test environment?

Third, is there some way to programmatically get my memory usage? Or at least the usage for a UIImage object? I've scoured the docs and don't see anything about that.

Upvotes: 2

Views: 1782

Answers (1)

progrmr
progrmr

Reputation: 77291

These functions give you an idea of your total memory usage and total free memory. I happen to have a 1 sec timer in my app, every second it logs used and free memory space (if either changed > 0.5mb), it helps me see better whats going on:

#import "mach/mach.h"

vm_size_t usedMemory(void) {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    return (kerr == KERN_SUCCESS) ? info.resident_size : 0;   // size in bytes
}

natural_t freeMemory(void) {
    mach_port_t           host_port = mach_host_self();
    mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
    vm_size_t              pagesize;
    vm_statistics_data_t   vm_stat;

    host_page_size(host_port, &pagesize);
    (void) host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); 
    return vm_stat.free_count * pagesize;
}

EDIT1 - function class_getInstanceSize gives you the size of an object instance, but I've never tried it and it probably doesn't dereference the ivars and rollup their usage. But maybe it can help you.

EDIT2 This function gives you the size of a UIImage:

size_t sizeofUIImage(UIImage* image) {
    return CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
}

EDIT4 Here is a bit of code from my 1 sec timer handler method, it helps see how your app is doing before you have memory problems. Of course, free memory varies depending on what background tasks are running (Safari, Mail, etc) which you don't have much control over:

void logMemUsage(void) {
    // compute memory usage and log if different by >= 100k
    static long prevMemUsage = 0;
    long curMemUsage = usedMemory();
    long memUsageDiff = curMemUsage - prevMemUsage;

    if (memUsageDiff > 100000 || memUsageDiff < -100000) {
        prevMemUsage = curMemUsage;
        NSLog(@"Memory used %7.1f (%+5.0f), free %7.1f kb", 
            curMemUsage/1000.0f, memUsageDiff/1000.0f, freeMemory()/1000.0f);
    }
}

Credits: memory free/used functions derived from here and here.

Upvotes: 4

Related Questions