My app makes use of lots of scaled images in animations. To avoid skipping frames, I scale my images and save them, before running the animation. Here is my code to save images:
+ (void)saveImage:(UIImage *)image withName:(NSString *)name {
NSData *data = UIImagePNGRepresentation(image);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex:0];
NSString *fullPath = [directory stringByAppendingPathComponent:name];
[fileManager createFileAtPath:fullPath contents:data attributes:nil];
}
Unfortunately, when I call this function repeatedly, I have memory problems. I guess I'm trying to save about 10MB worth of images. I'm thinking that perhaps the problem is with the autoreleased variables--perhaps I should alloc the data, and release at the end. But I can't find an alloc version of UIImagePNGRepresentation. Can anyone help?
UIImagePNGRepresentation returns an autoreleased NSData object. In other words, the data allocated will only be dealloced once you get to the release (or drain) call of the nearest enclosing NSAutoreleasePool block.
If you are calling the above code from within a loop, it's possible your code is never getting the chance to autorelease all that memory. In this sort of situation, you can enclose your calls in your own NSAutoreleasePool:
for (int i = 0; i < 10; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self saveImage:someImage withName:#"someName.png"];
[pool drain];
}
N.B. I believe working with PNGs this way is quite slow compared to using JPGs (UIImageJPGRepresentation). Just FYI.
What does the outer loop look like? If it's something like:
for(n = 0; n < 1000; n++)
{
... something ...
[class saveImage:image withName:name];
}
Then leaving things in the autorelease pool could be your problem. The autorelease pool is drained only when the call stack completely unwinds back to the run loop (since otherwise you wouldn't be able to use autoreleased things as return results). Given that you're not releasing anything, you might try modifying your code to:
+ (void)saveImage:(UIImage *)image withName:(NSString *)name {
// create a local autorelease pool; this one will retain objects only
// until we drain it
NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
NSData *data = UIImagePNGRepresentation(image);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex:0];
NSString *fullPath = [directory stringByAppendingPathComponent:name];
[fileManager createFileAtPath:fullPath contents:data attributes:nil];
// drain the pool, which acts like release in reference counted environments
// but also has an effect in garbage collected environments
[localPool drain];
}
So, for each save of the image you create your own autorelease pool. The most recently initialised autorelease pool automatically sets itself to catch all autoreleased objects from then on. In a garbage collected environment like iOS, calling 'drain' on it causes it to be deallocated and all objects it holds instantly to be released.
Related
I am trying to save an array of images to the documents folder. I managed to save an image as NSData and retrieve it using the method below, but saving an array seems to be beyond me. I've looked at several other questions that relate and it seems I'm doing everything right.
Adding the image as NSData and saving the image:
[imgsData addObject:UIImageJPEGRepresentation(img, 1.0)];
[imgsData writeToFile:dataFilePath atomically:YES];
Retrieving the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"imgs.dat"];
[self setDataFilePath:path];
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:dataFilePath])
imgsData = [[NSMutableArray alloc] initWithContentsOfFile:dataFilePath];
So, writing an image as NSData using the above works, but not an array of images as NSData. It inits the array, but it has 0 objects, which isn't correct, since the array I am saving has several. Does anyone have any ideas?
First of all, you should brush up Cocoa Memory Management, the first line of code is a little bit of a worry.
For data serialisation, you may like to have a go with NSPropertyListSerialization. This class serialises arrays, dictionaries, strings, dates, numbers and data objects. It has an error reporting system, unlike the initWithContentsOfFile: methods. The method names and arguments are a bit long to fit on one line, so sometimes you may see them written with Eastern Polish Christmas Tree notation. To save your imgsData object, you can use:
NSString *errString;
NSData *serialized =
[NSPropertyListSerialization dataFromPropertyList:imgsData
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&errString];
[serialized writeToFile:dataFilePath atomically:YES];
if (errString)
{
NSLog(#"%#" errString);
[errString release]; // exception to the rules
}
To read it back in, use
NSString *errString;
NSData *serialized = [NSData dataWithContentsOfFile:dataFilePath];
// we provide NULL for format because we really don't care what format it is.
// or, if you do, provide the address of an NSPropertyListFormat type.
imgsData =
[NSPropertyListSerialization propertyListFromData:serialized
mutabilityOption:NSPropertyListMutableContainers
format:NULL
errorDescription:&errString];
if (errString)
{
NSLog(#"%#" errString);
[errString release]; // exception to the rules
}
Check the contents of errString to determine what went wrong. Keep in mind that these two methods are being deprecated in favour of the dataWithPropertyList:format:options:error: and propertyListWithData:options:format:error: methods, but these were added in Mac OS X 10.6 (I'm not sure if they're available on iOS).
-(void)LoadOriginalListFromFile
{
NSMutableArray *temp;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"shopes.dat"];
//2.check if file exists
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:path])
{
//open it and read it
NSLog(#"shopes.dat file found. reading into memory");
NSMutableData *theData;
NSKeyedUnarchiver *decoder;
//3. decode the file into memory
theData = [NSData dataWithContentsOfFile:path];
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:theData];
temp = [[NSMutableArray alloc]init];
temp = [decoder decodeObjectForKey:#"m_OriginalArray"];
//4. add object to original list
NSEnumerator *enumerator = [temp objectEnumerator];
id anObject;
while (anObject = [enumerator nextObject])
{
[m_OriginalArray addObject:anObject];
}
//[temp release]; // here is the problem!!!!!
[decoder finishDecoding];
[decoder release];
}
else
{
NSLog(#"shopes.dat file not found");
}
}
I have problem with the temp object.
what i want to do is to release the object before the function end but if i do so when the app is launch i get ERROR_BAD_ACSS , i cant understand why?
i alloc the temp object then i add all the objects in the temp array to my m_OriginalArray i also tryied to retain the objects but no lack.
You allocate the object here:
temp = [[NSMutableArray alloc]init];
but then immediately replace it with a difference object returned from the NSKeyedUnarchiver:
temp = [decoder decodeObjectForKey:#"m_OriginalArray"];
The new object will be autoreleased, so there is no need to release it. You can simply remove the first line (the NSMutableArray alloc & init one) completely, as you are not using that object.
You're creating "temp" twice here. First you're alloc/init'ing it, in which case the release would make sense. But then that instance is getting discarded, and replaced with the return value from [decoder decodeObjectForKey: #"m_originalArray"]. That new instance is autoreleased, and so when you release it manually, you're setting it up to crash when the autorelease pool drains. Simply get rid of the first assignment, and the matching release, and you won't leak or crash.
In my app, I have a bunch of different image packs to download. The images are downloaded from my website one by one. The packs contain anywhere from 100-1500 images and each image is about 100KB-200KB.
When downloading the images, I have a method that selects the images to download, then I have a second method that does the actual downloading based on parameters sent from the main method. Here is the code for the download method:
-(BOOL) downloadImageName:(NSString *)imageName ImageGroup:(NSString *)imageGroup AndRow:(int)row {
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSURL *downloadURL = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.website.com/%#_0001.jpg",imageName]];
NSData *data = [NSData dataWithContentsOfURL:downloadURL];
NSString *savePath = [NSString stringWithFormat:#"%#/%#^%#_%#.jpg",docsPath,currentServerCycle,imageGroup,imageName];
BOOL downloaded = [data writeToFile:savePath atomically:YES];
if (downloaded)
return YES;
}
else {
return NO;
}
}
The problem I am having is this:
When I run this with performance tools looking at allocations I'm seeing that the app is keeping mallocs (NSConcreteData) each time an image is downloaded and only releasing them when the main method (the one calling this download method) completes. Thats fine for the smaller image packs, but the larger ones are obviously crashing after my total allocations hit something like 300+MB (the normal amount of allocations in my app is about 3mb).
They arent leaking, because once the image pack is downloaded and the method ends, all the mallocs are gone.
I have tried manually allocing and releasing the NSData *data but it has no effect.
Perhaps the autorelease pool the objects are in is not being drained until the main method returns. You might try adding NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; at the beginning of your download method, then [pool drain]; right before you return from the download method. You need to make sure you release the pool before you return no matter where you return otherwise you'll leak the pool.
Also, just as a point of standard Objective-C style, your method should be named:
(BOOL)downloadImageName:(NSString *)imageName imageGroup:(NSString *)imageGroup andRow:(int)row
with lowercase letters to start the "imageGroup:" and "andRow:" argument names.
I had the following code that takes in 14 mb or more of image data encoded in base4 string and converts them to jpeg before writing to a file in iphone. It crashes my program giving the following error :
Program received signal: “0”.
warning: check_safe_call: could not restore current frame
I tweak my program and it can process a few more images before the error appear again. My coding is as follows:
// parameters is an array where the fourth element contains a list of images in base64 >encoded string
NSMutableArray *imageStrList = (NSMutableArray*) [parameters objectAtIndex:5];
while (imageStrList.count != 0) {
NSString *imgString = [imageStrList objectAtIndex:0];
// Create a file name using my own Utility class
NSString *fileName = [Utility generateFileNName];
NSData *restoredImg = [NSData decodeWebSafeBase64ForString:imgString];
UIImage *img = [UIImage imageWithData: restoredImg];
NSData *imgJPEG = UIImageJPEGRepresentation(img, 0.4f);
[imgJPEG writeToFile:fileName atomically:YES];
[imageStrList removeObjectAtIndex:0];
}
I tried playing around with UIImageJPEGRepresentation and found out that the lower the value, the more image it can processed but this should not be the way. I am wondering if there is anyway to free up memory of the imageStrList immediately after processing each image so that it can be used by the next one in the line.
Your problem is that the UIImages and NSDatas are piling up in memory in a loop. Normally the autorelease pool would take care of this at the end of the event loop, but you're not letting it get that far. You have two choices:
Explicitly allocate/init and then release when done the UIImage and NSData objects, rather than using the +class methods.
Create an explicit autorelease pool inside the loop, and drain it at the end. Do this with:
NSAutoreleasePool * myPool = [[NSAutoreleasePool alloc] init];
[myPool release];
NSMutableArray *imageStrList = (NSMutableArray*) [parameters objectAtIndex:5];
while (imageStrList.count != 0) {
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
NSString *imgString = [imageStrList objectAtIndex:0];
// Create a file name using my own Utility class
NSString *fileName = [Utility generateFileNName];
NSData *restoredImg = [NSData decodeWebSafeBase64ForString:imgString];
UIImage *img = [UIImage imageWithData: restoredImg];
NSData *imgJPEG = UIImageJPEGRepresentation(img, 0.4f);
[imgJPEG writeToFile:fileName atomically:YES];
[imageStrList removeObjectAtIndex:0];
[pool release];
}
You should create another autoreleasepool inside the loop because objects created inside loop won't be released until the program get out from the loop.
Auto release pool inside the loop will release all objects created inside the loop every time.
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"DB"];
NSString *fileName = [newWordbookName stringByAppendingString:#".csv"];
NSString *fullPath = [databasePath stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
[databasePath release];
//[fileName release]; Error!
//[fullPath release]; Error!
//NSLog(#"#1 :databasePath: %d",[databasePath retainCount]);
//NSLog(#"#1 :fileName: %d",[fileName retainCount]);
//NSLog(#"#1 :fullPath: %d",[fullPath retainCount]);
I'm using this code and want to release NSString* ..
so, I declare fileName, fullPath, and databasePath of NSString.
database is released but fileName, fullpath doesn't release. I don't know why it happens.
I know that NSArray is Autoreleased. But is documentsDirectory autoreleased?
(newWordbookName is nsstring type)
I hope that I look through a document about iPhone memory management.
By convention the only two cases when a method returns a retained object are constructors i.e. alloc, new etc. and object-copying methods (containing copy in their name).
In all other cases the object is expected to be autoreleased, unless explicitly stated otherwise in the documentation.
This is the complete memory management documentation:
Cocoa Memory Management
You should not be calling release on any of the objects in the above code.
The reason the NSArray is autorelease'd is the same reason all the other objects are autorelease'd: the methods that assigned them their values called autorelease on them before they returned. In general, you can assume methods return autorelease'd objects if they do not have the word "create" or "new" in them. That is the general Cocoa convention. (Although 3rd party code may be goofy and do things differently, so caveat programmer).
You only really need to worry about objects you alloc or copy yourself; in other words, pair every alloc or copy with a release or autorelease.