15
votes

From profiling with Instruments I have learned that the way I am saving images to disk is resulting in memory spikes to ~60MB. This results in the App emitting low memory warnings, which (inconsistently) leads crashes on the iPhone4S running iOS7.

I need the most efficient way to save an image to disk.

I am currently using this code

+ (void)saveImage:(UIImage *)image withName:(NSString *)name {
    NSData *data = UIImageJPEGRepresentation(image, 1.0);
    DLog(@"*** SIZE *** : Saving file of size %lu", (unsigned long)[data length]);
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:name];
    [fileManager createFileAtPath:fullPath contents:data attributes:nil];
}

Notes:

  1. Reducing the value of the compressionQuality argument in UIImageJPEGRepresentation does not reduce the memory spike significantly enough. e.g. compressionQuality = 0.8, reduced the memory spike by 3MB on average over 100 writes. However, it does reduce the size of the data on disk (obviously)but this does not help me.

  2. UIImagePNGRepresentation in place of UIImageJPEGRepresentation is worse for this. It is slower and results in higher spikes.

Is it possible that this approach with ImageIO would be more efficient? If so why?

If anyone has any suggestions it would be great. Thanks

Edit:

Notes on some of the points outlined in the questions below.

a) Although I was saving multiple images, I was not saving them in a loop. I did a bit of reading around and testing and found that an autorelease pool wouldn't help me.

b) The photos were not 60Mb in size each. They were photos taken on the iPhone 4S.

With this in mind I went back to trying to overcome what I thought the problem was; the line NSData *data = UIImageJPEGRepresentation(image, 1.0);.

The memory spikes that were causing the crash can be seen in the screenshot below. They corresponded to when UIImageJPEGRepresentation was called. I also ran Time Profiler and System Usage which pointed me in the same direction.

enter image description here

Long story short, I moved over to AVFoundation and took the photo image data using

photoData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];

Which returns an object of type NSData, I then used this as the data to write using NSFileManager.

This removes the spikes in memory completely.

i.e

[self saveImageWithData:photoData];

where

+ (void)saveImageWithData:(NSData *)imageData withName:(NSString *)name {
    NSData *data = imageData;
    DLog(@"*** SIZE *** : Saving file of size %lu", (unsigned long)[data length]);
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:name];
    [fileManager createFileAtPath:fullPath contents:data attributes:nil];
}

PS: I have not put this as an answer to the question incase people feel it does not answer the Title "Most memory efficient way to save a photo to disk on iPhone?". However, if the consensus is that it should be I can update it.

Thanks.

3
You're talking about both in-memory and on-disk data. Only in-memory cause the app to be force terminated. What do you keep in memory that you don't need. If the image is massive then you need to use it differently...Wain
I know that. I also state that in the question. i.e. "However, it does reduce the size of the data on disk (obviously)but this does not help me." Your comment "If the image is massive then you need to use it differently" doesn't help.Ríomhaire
I don't think the saveImage:withName is a problem. It may be how you call this function. Provide the code which use this function and people can help you.sahara108
So concentrate on the in-memory situation and give more details of how many images, how they are processed before you come to saving, how big they are...Wain
I'd agree. Post it as an answer; if something better comes along, you can always move the checkmark.dokkaebi

3 Answers

4
votes

Using UIImageJPEGRepresentation requires that you have the original and final image in memory at the same time. It may also cache the fully rendered image for a while, which would use a lot of memory.

You could try using a CGImageDestination. I do not know how memory efficient it is, but it has the potential to stream the image directly to disk.

+(void) writeImage:(UIImage *)inImage toURL:(NSURL *)inURL withQuality:(double)inQuality {
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL( (CFURLRef)inURL , kUTTypeJPEG , 1 , NULL );
    CFDictionaryRef properties = (CFDictionaryRef)[NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:inQuality] forKey:kCGImageDestinationLossyCompressionQuality];
    CGImageDestinationAddImage( destination , [inImage CGImage] , properties );
    CGImageDestinationFinalize( destination );
    CFRelease( destination );
}
2
votes

Are your images actually 60MB compressed, each? If they are, there's not a lot you can do if you want to save them as a single JPEG file. You can try rendering them down to smaller images, or tile them and save them to separate files.

I don't expect your ImageIO code snippet to improve anything. If there were a two-line fix, then UIImageJPEGRepresentation would be using it internally.

But I'm betting that you don't get 60MB from a single image. I'm betting you get 60MB from multiple images saved in a loop. And if that's the case, then there is likely something you can do. Put an @autoreleasepool{} inside your loop. It is quite possible that you're accumulating autoreleased objects, and that's leading to the spike. Adding a pool inside your loop allows it to drain.

-1
votes

Try to use NSAutoReleasePool and drain the pool once u finish writing the data.