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:
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.
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.
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.
Tap Settings > [your name] > iCloud > Photos. Turn on iCloud Photos. Select Optimize iPhone Storage to save space on your device.
Back up your photos You can cut down on storage space without actually deleting your photos. Cloud storage systems like Google+ and Dropbox will automatically save your snapshots, so you can delete them from your Photos app.
Connect your iPhone, iPad or iPod touch to your Mac with a USB cable. Open the Photos app on your computer. The Photos app shows an Import screen with all of the photos and videos that are on your connected device. If the Import screen doesn't appear automatically, click the device's name in the Photos sidebar.
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 );
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With