views:

452

answers:

3

I'm building an array of dictionaries (called keys) in my iphone application to hold the section names and row counts for a tableview.

the code looks like this:

[self.results removeAllObjects];
[self.keys removeAllObjects];

NSUInteger i,j = 0;
NSString *key = [NSString string];
NSString *prevKey = [NSString string];

if ([self.allResults count] > 0)
{
    prevKey = [NSString stringWithString:[[[self.allResults objectAtIndex:0] valueForKey:@"name"] substringToIndex:1]];

    for (NSDictionary *theDict in self.allResults)
    {
        key = [NSString stringWithString:[[theDict valueForKey:@"name"] substringToIndex:1]];

        if (![key isEqualToString:prevKey])
        {
            NSDictionary *newDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                           [NSNumber numberWithInt:i],@"count",
                                           prevKey,@"section",
                                           [NSNumber numberWithInt:j],
                                           @"total",nil];

            [self.keys addObject:newDictionary];
            prevKey = [NSString stringWithString:key];
            i = 1;
        }
        else
        {
            i++;
        }
        j++;

    }

    NSDictionary *newDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                   [NSNumber numberWithInt:i],@"count",
                                   prevKey,@"section",
                                   [NSNumber numberWithInt:j],
                                   @"total",nil];

    [self.keys addObject:newDictionary];

}

[self.tableview reloadData];

The code works fine first time through but I sometimes have to rebuild the entire table so I redo this code which orks fine on the simulator, but on my device the program bombs when I execute the reloadData line :

malloc: *** mmap(size=3772944384) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
malloc: *** mmap(size=3772944384) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
Program received signal:  “EXC_BAD_ACCESS”.

If I remove the reloadData line the code works on the device.

I'm wondering if this is something to do with the way I've built the keys array (ie using autoreleased strings and dictionaries).

A: 

If at all possible i would highly recommend not using autoreleased strings and objects in general as they also decrease performance. Everything in your code set looks correct except for the odd statement up top.

the first 2 lines remove all the objects from the arrays then you assign pointers to new self.keys and self.results

assign the self.keys and self.results only once and when reloading data simply call removeAllObjects.

davydotcom
Sound advice, but not the problem here.
bbum
You're proposing optimizing for performance at the cost of readability without testing. That's bad advice. Autoreleased objects will usually cost a fraction of a percent in performance, and would (among other things) fix several memory leaks in this code that you've missed and declared "everything looks correct."Autorelease is there for a reason: Getting all this stuff right without a simple pattern is *hard*.
Steven Fisher
You're right about the 2 lines at the top (re-allocating keys and results). They were left in from a test I was running. I've removed them from the code now.
Joe
A: 

An array of dictionaries called keys… [self.keys addObject:newDictionary].. needs.. lie.. down.

mustISignUp
Thanks for the downvote, I see now that it's a brilliant naming scheme, the problem is elsewhere.
mustISignUp
+13  A: 

The error message is giving you a very clear reason why the allocation failed:

malloc: *** mmap(size=3772944384) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
malloc: *** mmap(size=3772944384) failed (error code=12)
*** error: can't allocate region

Specifically, the size is 3,772,944,384; almost 4GB. I.e. you are asking malloc() to allocate something that is causing malloc to think it needs to memory map (mmap) nearly 4GBs of address space!

Now, if your input strings are really darned huge, then your code will be bloating the autorelease pools as davydotcom says, but they'd have to be really really huge to cause that to happen. And if it is that huge, then are you ending up with 10s of thousands or hundreds of thousands of rows in your table? If so, don't do that -- it is too hard for the user to work with.

As the error says, set a breakpoint on malloc_error and post a backtrace.

Note that this:

NSString *key = [NSString string];
NSString *prevKey = [NSString string];

Is nonsense.

OK -- a second glance of your code indicates that a review of the Objective-C guide and the Cocoa memory management guide would be useful.

In the first four lines of code, you both leak the previous and over-retain the new self.keys and self. self.results (assuming both are retain properties, as they should be).

Try, also, to use Build & Analyze on your code.

bbum
+1 for the usability concerns
Victor Jalencas
the strings are very small and there are roughly 500 rows in the table. I'll try the breakpoint...
Joe
bbum
the malloc_break doesn't help much other than i can see a function called large_and_huge_malloc after UIsectionRowData refreshWithSection:tableView:tableViewRowData.
Joe
Sounds like something is hosed and has returned way too many rows to the table view....
bbum
Interestingly, when I run Leaks I don't get the crash?????I've tried this 4 times - run Leaks no crash, without Leaks it crashes.
Joe
That sounds more and more like something is trashing memory, causing an unfortunate value to be returned to the table view. First thing to do is to "build and analyze" your code, fixing the problems identified.
bbum
Ok, I ran "build and analyze" and got a few errors, mostly UIAlertViews that I didn't release. I ran the code and it worked!I re-instated the old code line by line until I tracked down the error and the offending line is:NSUInteger i,j = 0;This value of "i" was marked by "build and analyze" as "Pass-by-value argument in message expression is undefined" in the line where newDictionary is defined. i is not being initialised properly.to fix it I changed the line to :NSUInteger i = 0,j = 0;Thanks to everyone for all your help, especially bbum."build and analyze" is my new favourite tool!
Joe