When i am storing larger size images in core data using for loop i am receiving memory warning message by the didReceiveMemoryWarning method when
iteration count is 300. Now based on memory warning i can display the user with the alert that "memory is fully please sync your images". But my problem is i am unable to get memory warning greater that 300. i.e i am getting memory warning exactly for 300th iteration.above 300 and below 300 i am not getting memory warning.
this is code which i used
for (int i=0;i<=300;i++)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *persistentStorePath = [documentsDirectory stringByAppendingPathComponent:#"DetailsRegister.sqlite"];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EMpDetails" inManagedObjectContext:context];
NSManagedObject *newDevice=[[NSManagedObject alloc]initWithEntity:entity insertIntoManagedObjectContext:context];
UIImage *image = [UIImage imageNamed:#"image.png"];
imageview.image=image;
[self SaveImage:image];
dataImage = UIImageJPEGRepresentation(image, 0.0);
[newDevice setValue:dataImage forKey:#"image"]; // obj refers to NSManagedObject
error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
CoreData isn't really an ideal place to store image data.
I tend to just store the imageData (or just actual images if they are not sensitive) in the documents folder and then just store an imageURL against the persisted object.
That way you can just return the Image for a URL that way, much better performance.
Again drawing on Marcus S. Zarra's Core Data: Data Storage and Management for iOS, OS X, and iCloud (2nd edition), one recommendation is as follows:
Small Binary Data [...] anything smaller than 100 kilobytes [...] When working with something this small, it is most
efficient to store it directly as a property value in its
corresponding table.
Medium Binary Data [...] anything larger than 100
kilobytes and smaller than 1 megabyte in size [...] Data
of this size can also be stored directly in the repository. However,
the data should be stored in its own table on the other end of a
relationship with the primary tables.
Large Binary Data [...] greater than 1
megabyte in size [...] Any binary data of this size
should be stored on disk as opposed to in the repository. When working
with data of this size, it is best to store its path information
directly in the primary entity [...] and store the binary data in a known location on disk (such as in the Application Support subdirectory for your application).
For more details, you should get the book (disclosure: not affiliated).
Related
I'm currently building an app that pulls large JSON file via an API request.
During the download-decoding-storing the data I get memory warnings (over 500MB).I found a solution to avoid to overload the memory and keep it at most at 300MB by adding #autoreleasepool { } function manually.
#autoreleasepool {
NSString * result = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&err];//150
decodeData = [[NSData alloc] initWithBase64EncodedString:result options:0];//100
}
#autoreleasepool {
NSString * decodeString = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];//100
NSError * jsonError;
NSData * objectData = [decodeString dataUsingEncoding:NSUTF8StringEncoding];//100
json = [NSJSONSerialization JSONObjectWithData:objectData options:NSJSONReadingMutableContainers error:&jsonError];//50
if(!jsonError){
[defults setObject:json forKey:#"data_object"];//50
}
}
Is there a better way of doing this for memory management?
Placing an #autorelease block around code that generates lots of throw-away (autoreleased) objects is not only valid, but recommended. This obviously also applies to few, large objects :)
Code which is running on the main thread has an autorelease pool available, but it may not be enough. The pool is drained at the bottom of the runloop, and if many autoreleased objects are created in a single runloop cycle, you may need a pool specifically to cleanup these objects to avoid running out of memory. This happens often with loops, and it's recommended that loop bodies be #autorelease blocks in such situations.
In terms of your specific issue, 300MB for one JSON structure is pushing it. If at all possible, you should try and break that up into smaller objects and parse them separately.
I am trying to play a video stored in Core data. After fetch it shows, there is an object and objects.video returns a value but the dataString prints out to be null. I am not sure what I maybe doing wrong. Is it a right way to play video or is there something I could have done better?
I have single object in Core Data.
I have stored as video as NSData in Core data. I want to get that stored video and play. Is there any other way I can do it?
_context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Activity" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
// Specify how the fetched objects should be sorted
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"level"
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray *fetchResults = [_context executeFetchRequest : fetchRequest error : &error];
if(fetchRequest == nil){
NSLog(#"Nothing fetched");
}
for (Activity *objects in fetchResults){
NSLog(#"%#",objects.video);
prints-> External Data Reference: <self = 0x7bf48750 ; path = FF54B18E-10B3-4B04-81D4-55AC5E2141B9 ; length = 504426>
NSString *dataString = [[NSString alloc] initWithData:objects.video encoding:NSUTF8StringEncoding];
NSLog(#"%#",dataString);
NSURL *movieURL = [NSURL URLWithString:dataString];
NSLog(#"%#",movieURL);
_moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
[_moviePlayer.view setFrame:CGRectMake (20, 20, 200 , self.view.bounds.size.height/2)];
[self.view addSubview:_moviePlayer.view];
[_moviePlayer play];
}
NSLog(#"%i",fetchResults.count);
It will just be a binary property on the managed object. You call the property and it returns NSData. From there you can do whatever you want with the NSData while it is memory. Your problem is that you can't convert the NSData into a NSURL. A NSURL is a reference to data where NSData is the actual data.
What you need to do is store the video file on disk, outside of the SQLite file. Then store a reference to it (aka a url) in Core Data. That will allow you to use the video file with the movie player.
As others have said, storing the video in the SQLite file is a bad idea. It will wreck the performance of Core Data.
Update 1
Thanks. I have not saved the video directly to core data instead just copied the video url and saved it as string in core data. Should I create a different folder for the app to store the videos whenever I create a copy of the videos the users use so even if they delete the original video that was uploaded to core data, the copy remains intact and thus maintaining integrity of the Objects in Core Data.
Your comment is unclear. According to your question, you are storing the actual video in Core Data. Based on the output you showed, you stored the file in Core Data. Have you changed that? If so, you should store the video file in a known location and store the relative URL to that location in core data as a string. Then you build the full URL from that when you are ready to use it. Since the sandbox can change you can't store the entire URL as it will go stale.
I receive a memory warning when using UIImageJPEGRepresentation, is there any way to avoid this? It doesn't crash the app but I'd like to avoid it if possible. It does intermittently not run the [[UIApplication sharedApplication] openURL:url];
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
NSData *imageToUpload = UIImageJPEGRepresentation(image, 1.0);
// code that sends the image to a web service (omitted)
// on success from the service
// this sometime does not get run, I assume it has to do with the memory warning?
[[UIApplication sharedApplication] openURL:url];
}
Using UIImageJPEGRepresentation (in which you are round-tripping the asset through a UIImage) can be problematic, because using a compressionQuality of 1.0, the resulting NSData can actually be considerably larger than the original file. (Plus, you're holding a second copy of the image in the UIImage.)
For example, I just picked a random image from my iPhone's photo library and the original asset was 1.5mb, but the NSData produced by UIImageJPEGRepresentation with a compressionQuality of 1.0 required 6.2mb. And holding the image in UIImage, itself, might take even more memory (because if uncompressed, it can require, for example, four bytes per pixel).
Instead, you can get the original asset using the getBytes method:
static NSInteger kBufferSize = 1024 * 10;
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSURL *url = info[UIImagePickerControllerReferenceURL];
[self.library assetForURL:url resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *representation = [asset defaultRepresentation];
long long remaining = representation.size;
NSString *filename = representation.filename;
long long representationOffset = 0ll;
NSError *error;
NSMutableData *data = [NSMutableData data];
uint8_t buffer[kBufferSize];
while (remaining > 0ll) {
NSInteger bytesRetrieved = [representation getBytes:buffer fromOffset:representationOffset length:sizeof(buffer) error:&error];
if (bytesRetrieved <= 0) {
NSLog(#"failed getBytes: %#", error);
return;
} else {
remaining -= bytesRetrieved;
representationOffset += bytesRetrieved;
[data appendBytes:buffer length:bytesRetrieved];
}
}
// you can now use the `NSData`
} failureBlock:^(NSError *error) {
NSLog(#"assetForURL error = %#", error);
}];
}
This avoids staging the image in a UIImage and the resulting NSData can be (for photos, anyway) considerably smaller. Note, this also has an advantage that it preserves the meta data associated with the image, too.
By the way, while the above represents a significant memory improvement, you can probably see a more dramatic memory reduction opportunity: Specifically, rather than loading the entire asset into a NSData at one time, you can now stream the asset (subclass NSInputStream to use this getBytes routine to fetch bytes as they're needed, rather than loading the whole thing into memory at one time). There are some annoyances involved with this process (see BJ Homer's article on the topic), but if you're looking for dramatic reduction in the memory footprint, that's the way. There are a couple of approaches here (BJ's, using some staging file and streaming from that, etc.), but the key is that streaming can dramatically reduce your memory footprint.
But by avoiding UIImage in UIImageJPEGRepresentation (which avoids the memory taken up by the image as well as the larger NSData that UIImageJPEGRepresentation yields), you might be able to make considerably headway. Also, you might want to make sure that you don't have redundant copies of this image data in memory at one time (e.g. don't load the image data into a NSData, and then build a second NSData for the HTTPBody ... see if you can do it in one fell swoop). And if worst comes to worse, you can pursue streaming approaches.
in ARC: Just put your code inside small block of #autoreleasepool
#autoreleasepool {
NSData *data = UIImageJPEGRepresentation(img, 0.5);
// something with data
}
Presented as an answer for formatting and images.
Use instruments to check for leaks and memory loss due to retained but not leaked memory. The latter is unused memory that is still pointed to. Use Mark Generation (Heapshot) in the Allocations instrument on Instruments.
For HowTo use Heapshot to find memory creap, see: bbum blog
Basically the method is to run Instruments allocate tool, take a heapshot, run an iteration of your code and take another heapshot repeating 3 or 4 times. This will indicate memory that is allocated and not released during the iterations.
To figure out the results disclose to see the individual allocations.
If you need to see where retains, releases and autoreleases occur for an object use instruments:
Run in instruments, in Allocations set "Record reference counts" on (For Xcode 5 and lower you have to stop recording to set the option). Cause the app to run, stop recording, drill down and you will be able to see where all retains, releases and autoreleases occurred.
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.
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 );
}
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.
Try to use NSAutoReleasePool and drain the pool once u finish writing the data.
I am downloading videos and pdfs from server. When my device memory is low say 50MB left , then while downloading my app crashes. How to check if the downloaded data will be more than memory space left in phone?
And if the data is more than space available , how to stop the downloading?
Is anyone aware of simple solution?
You can get the available disk space before you start the request:
NSError *error = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error];
NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
When you get the NSURLResponse for the request you are making it should (hopefully) have a content size header so you know how much data to expect. If the amount of data is too high you can cancel the request (assuming you're using NSURLConnection with a delegate) and alert the user.