iPhone: Low memory crash - iphone

Once again I'm hunting memory leaks and other crazy mistakes in my code. :)
I have a cache with frequently used files (images, data records etc. with a TTL of about
one week and a size limited cache (100MB)). There are sometimes more then 15000 files in a
directory. On application exit the cache writes an control file with the current cache
size along with other useful information. If the applications crashes for some reason
(sh.. happens sometimes) I have in such case to calculate the size of all files on application start to make sure I know the cache size.
My app crashes at this point because of low memory and I have no clue why.
Memory leak detector does not show any leaks at all. I do not see any too. What's wrong
with the code below? Is there any other fast way to calculate the total size of all files
within a directory on iPhone? Maybe without to enumerate the whole contents of the directory?
The code is executed on the main thread.
NSUInteger result = 0;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDirectoryEnumerator *dirEnum = [[[NSFileManager defaultManager] enumeratorAtPath:path] retain];
int i = 0;
while ([dirEnum nextObject]) {
NSDictionary *attributes = [dirEnum fileAttributes];
NSNumber* fileSize = [attributes objectForKey:NSFileSize];
result += [fileSize unsignedIntValue];
if (++i % 500 == 0) { // I tried lower values too
[pool drain];
}
}
[dirEnum release];
dirEnum = nil;
[pool release];
pool = nil;
Thanks,
MacTouch

Draining the pool "releases" it, it doesn't just empty it. Think of autorelease pools as stacks, so you have popped yours, meaning that all these new objects are going into the main autorelease pool and not being cleaned up until it gets popped. Instead, move the creation of your autorelease pool to inside the loop. You can do something like
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
int i = 0;
while( shouldloop ) {
// do stuff
if( ++i%500 == 0 ) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
}
}
[pool drain];

Related

iPhone memory crash: signal 0

I'm developing an iPhone app which displays some tiles from a map. I've got a thread which loads tile's from internet or filesystem. This is an endless thread
while( true ){ //get tile into cache }
Unfortunateley after displaying much tiles the application chrashes with signal 0, which means that im out of memory..
My idea is that in this endless thread the tiles are loaded as autorelease, and are nog autoreleased..
Basically im doing this in my endless thread:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
while( true ){
UIImage * img = nil;
NSFileHandle * tile = [NSFileHandle fileHandleForReadingAtPath:filePath];
if( tile ){
NSData * data = [tile readDataToEndOfFile];
if( data ){
img = [[UIImage alloc] initWithData:data];
local = YES;
}
}
if( img == nil ){
NSURL * url = [NSURL URLWithString: [NSString stringWithFormat:#"http://tile.openstreetmap.org/%#.png", todoKey ] ];
img = [[UIImage alloc] initWithData: [NSData dataWithContentsOfURL: url options:NSDataReadingMapped error:&error] ];
}
if( img != nil ){
[cache addObject:img]; //cache is an array of tiles
}
[img release];
[self cleanupCache]; //method to check if cache is full, and if so remove's objects from it
}
[pool release];
Im thinking that the autoreleasepool doesn't clean up the "dead" references from time to time, and all the tiles are kept in memory. When i check with instruments-leaks there aren't any memory leaks, but the 'live-memory' keeps adding up..
Any idea's why this is happening and how i can prevent it?
You should swap the first 2 lines of the program:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
while( true ){
...
[pool release];
}
should be
while( true ){
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
...
[pool release];
}
Currently, after the first trip through your loop, the NSAutoreleasePool you have created will be released. After this, all autoreleased objects will be added to a different NSAutoreleasePool higher in the call stack. I suspect this parent autorelease pool is never released while your program is running.
if( img != nil ){
[cache addObject:img];
cache still holds reference of all the objects you have added. You should probably remove objects which you wont be using.
I fixed the indention in your code, I hope I got it right. The improved indention makes one problem obvious:
You are releasing a already released object, your pool.
Try to move NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; inside the loop.
But most likely you should get rid of the loop in the first place.
Another problem is that you don't set img to nil. If there is a valid img in the first run of the loop the code will think it's valid for all other loop runs that follow. If you remove img somewhere from the cache (and the object img points to gets deallocated) you send a retain to a deallocated object when you try to add it to the cache.
Add img = nil; at the beginning of your loop.

subarrayWithRange with large data

iPad application
I have a large text file which I want to divide into several pieces and process them in a loop.
I used the following code:
NSArray * contentArray = [stringFromFile componentsSeparatedByString:#" "];
for(...){
contentRange = NSMakeRange(lastPosition , newLength);
NSArray * subContentArray = [contentArray subarrayWithRange:contentRange];
NSString *subContent = [subContentArray componentsJoinedByString:#" "];
....
//Process sub content here...
}
After running, I receive malloc error, code 12
Observing the Activity monitor, the memory and the VM size increases and until system memory is exhausted and the application crashes.
Is there any way to prevent this?
One way to work around this issue will be to use a custom NSAutoreleasePool to constantly clear temporarily allocated memory. Like so:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUInteger i=0;
for (...) {
... // your method contents
if (++i % 10 == 0) {
// you might want to play with the frequency of the releases, depending on the size of your loop
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
}
[pool release];
The autorelease pool thing will take care of the memory problem for the most part, but if the file is any size it might be slow.
It seems like scanning the original string in a loop getting substrings directly from the string would be faster. Also use at least 1/2 the memory, since you duplicate the string in the components array.
The code would use rangeOfString on the original string, until a substring of appropriate length was found, then process the substring, and onto the next one.
iPhone 3G phones only have about 5 - 15 MB of memory total for your application. You are on an iPad, though I see which does give you about 4x that amount.

Data Formatters temporarily unavailable, will re-try after a 'continue'

Here is the error message I get:
ContactsWithPN - start loop
Program received signal: “0”.
Data Formatters temporarily unavailable, will re-try after a 'continue'. (Unknown error loading shared library "/Developer/usr/lib/libXcodeDebuggerSupport.dylib")
Here is the code that causes this problem:
+(NSArray *) contactsWithPhoneNumbers{
NSArray *contacts = [ABContactsHelper contacts];
NSMutableArray *rv = [[NSMutableArray alloc] init];
NSLog(#"ContactsWithPN - start loop");
for (int i = 0; i< [contacts count] ; i++) {
ABContact * c = (ABContact*)[contacts objectAtIndex:i];
ABContact * fullContact = [ABContact contactWithRecordID:[c recordID]];
if ([[fullContact phoneArray] count] > 0) {
[rv addObject:fullContact];
}
}
NSLog(#"ContactsWithPN - end loop");
NSArray *ret = [[NSArray alloc] initWithArray:rv];
return ret;
}
In the View Controller that calls the said class method, I added the following code to see if memory warnings were being sent. They are not!
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
NSLog(#"InviteFriends - memory warning received");
}
Observations:
+ Found that error occurs at different points in time - sometimes at index 253, other times at 246..
+ Only happens on the IPhone - not the simulator (on the simulator, there are < 5 contacts)s
The fact that you haven't received the memory warning doesn't mean that it wasn't sent: Memory warnings are delivered to the main run loop; they will not be delivered while your function is still running.
Instead, consider looking at the phone console (Xcode->Organizer->Your phone->Console, or the equivalent in iPCU). If it says something like "memory level is critical" and mentions killing your app, then you've run out of memory. Additionally, when you run out of memory, the crash reporter writes a "low memory" crash log with "jettisoned" beside the processes that were killed; you should see these in the Organizer. (Since iOS 4's "multitasking", jettisoning also happens to background tasks.)
If it's due solely to the large pile of autoreleased objects, you can mitigate it to some extent with explicit autorelease pools:
for (int i = 0; i< [contacts count] ; i++) {
NSAutoreleasePool * pool = [NSAutoreleasePool new];
...
[pool drain]; pool = nil;
}
Your code also leaks ret and rv.
This error occurs when your app is running out of memory. You should read Apples Memory Management Guide.
First thing is that you have memory leaks in your code... Fix that like this
+(NSArray *) contactsWithPhoneNumbers{
NSArray *contacts = [ABContactsHelper contacts];
NSMutableArray *rv = [[NSMutableArray alloc] init];
NSLog(#"ContactsWithPN - start loop");
for (int i = 0; i< [contacts count] ; i++) {
ABContact * c = (ABContact*)[contacts objectAtIndex:i];
ABContact * fullContact = [ABContact contactWithRecordID:[c recordID]];
if ([[fullContact phoneArray] count] > 0) {
[rv addObject:fullContact];
}
}
NSLog(#"ContactsWithPN - end loop");
NSArray *ret = [[NSArray alloc] initWithArray:rv];
//You need to release rv since you dont need it any more as you have copied the contents to a new array ( this itself is a waste ) so you must release the old array
[rv release];
//Now when you return the array you must still not hold object ownership since the function dies after returning so you give it a delayed release which kicks in when the autorelease is flushed.
return [ret autorelease];
}
Normally autorelease is flushed at certain times decided by the OS however you can create your own pool to make sure that you don't waste resources. So Ideally you will make the call like this
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *contactArray = [[self contactsWithPhoneNumbers] retain];
[pool drain];
//This will release the object ownership held by the function
Finally so you do all this and you don't have memory leaks but you still get this error. The answer because the memory warning didn't get to you just as #tc. has said. So the simple answer is that the main run loop was clogged. What you can do is maybe do this operation in a separate thread to make sure that the main loop is not clogged... If you are on iOS 4+ you can do this easily by
dispatch_queue_t otherQueue = dispatch_queue_create("com.company.otherqueue", NULL);
dispatch_async(otherQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *contactArray = [[self contactsWithPhoneNumbers] retain];
[pool drain];
}
dispatch_release(otherQueue);
Now this necessarily does not mean that it will create a new thread however the os will manage the queue such that the main queue does not get blocked and you will receive the memory warning. From then on you must release the memory and make sure you dont go over.
I discover ABContact also leaks memory,see part of ABContactHelper code below.
+ (NSArray *) contacts
{
ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *thePeople = (NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:thePeople.count];
for (id person in thePeople)
{
[array addObject:[ABContact contactWithRecord:(ABRecordRef)person]];
}
[thePeople release];
//I think need CFRelease(addressBook); here
return array;
}

UIIMageView, warning: check_safe_call: could not restore current frame

I am changing the image in UIImageView based on accelerometer input. The images are stored in an array.
The application runs fine for a while and then crashes.
warning: check_safe_call: could not restore current frame
I am not using "UIImage ImageNamed" method when populating the array.
The total size of all images is around 12 Mb. But individual images are very light (<100 kb) and i am using only one image at a time from the array.
Just in case there are any autoreleases, I have allocated a NSAutoreleasePool in view did load and am draining it in the didReceiveMemoryWarning method (which does get called 2, 3 times before the app crashes?).
Following is the code that creates images array:
//create autorelease pool as we are creating many autoreleased objects
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *finalarr = [[NSMutableArray alloc] initWithCapacity:9];
NSLog(#"start loading");
for(int y = 0; y < 100; y+=10)
{
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:10];
for(int x = 0; x < 10; x++)
{
NSString *imageName = [[NSString alloc] initWithFormat:#"0%d",y + x];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
[arr addObject:img];
[imageName release];
[img release];
}
[finalarr addObject:arr];
[arr release];
}
NSLog(#"done loading");
// Override point for customization after app launch
viewController.imagesArray = finalarr;
[finalarr release];
//retain the array of images
[viewController.imagesArray retain];
//release the aurtorelease pool to free memory taken up by objects enqued for release
[pool release];
As the app runs smoothly for a while, which means array creation is definitely not the problem.
After this the following method is called from [accelerometer didAccelerate]
-(BOOL)setImageForX:(int)x andY:(int)y
{
[self.imageView setImage:(UIImage*)[(NSArray*)[self.imagesArray objectAtIndex:y] objectAtIndex:x]];
return TRUE;
}
So the only code being executed is the "UIImageView setImage". But no objects are being created here.
Please let me know if the code seems to have any leaks or i am doing something wrong.
Thanks,
Swapnil
NSAutoreleasePool should be allocated and released in the same method. Allocating it in one place and releasing it in another is usually very bad (the example code you pasted uses it correctly however)
Loading images into memory will decompress them; a 100KB compressed PNG could easily be 2MB uncompressed.
You also seem to have an extra retain call that shouldn't be there: [viewController.imagesArray retain]; (I assume the imagesArray property is marked copy or retain and not assign)

How do I create a local autorelease pool to save up memory?

Apple says that this is a good idea for saving memory. What would that look like in code?
Usualy you don't need to create autorelease pool, because system cares about this. But, sometimes you need to do this. It's usualy in big loops. Code would look like this:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i;
for (i = 0; i < 1000000; i++) {
id object = [someArray objectAtIndex:i];
// do something with object
if (i % 1000 == 0) {
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
}
[pool release];
Autorelease pools are kept as a stack: if you make a new autorelease pool, it gets added to the top of the stack, and every autorelease message puts the receiver into the topmost pool.