Reputation: 2609
This is my first time creating an iPhone App and I am having difficulty with the memory management because I have never had to deal with it before.
I have a UITableViewController and it all works fine until I try to scroll down in the simulator. It crashes saying that it cannot allocate that much memory. I have narrowed it down to where the crash is occurring:
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeue or create a cell
UITableViewCellStyle style = UITableViewCellStyleDefault;
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:@"BaseCell"];
if (!cell) cell = [[[UITableViewCell alloc] initWithStyle:style reuseIdentifier:@"BaseCell"] autorelease];
NSString* crayon;
// Retrieve the crayon and its color
if (aTableView == self.tableView)
{
crayon = [[[self.sectionArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] getName];
}
else
{
crayon = [FILTEREDKEYS objectAtIndex:indexPath.row];
}
cell.textLabel.text = crayon;
if (![crayon hasPrefix:@"White"])
cell.textLabel.textColor = [self.crayonColors objectForKey:crayon];
else
cell.textLabel.textColor = [UIColor blackColor];
return cell;
}
Here is the getName method:
- (NSString*)getName
{
return name;
}
name is defined as:
@property (nonatomic, retain) NSString *name;
Now sectionArray is an NSMutableArray with instances of a class that I created Term in it.
Term has a method getName that returns a NSString*. The problem seems to be the part of where crayon is being set and getName is being called. I have tried adding autorelease, release, and other stuff like that but that just causes the entire app to crash before even launching.
Also if I do:
cell.textLabel.text = @"test"; //crayon;
/*if (![crayon hasPrefix:@"White"])
cell.textLabel.textColor = [self.crayonColors objectForKey:crayon];
else
cell.textLabel.textColor = [UIColor blackColor];*/
Then I get no error whatsoever and it all scrolls just fine.
Thanks in advance for the help!
Edit:
Here is the full Log of when I try to run the app and the error it gives when it crashes:
[Session started at 2010-12-29 04:23:38 -0500.]
[Session started at 2010-12-29 04:23:44 -0500.] GNU gdb 6.3.50-20050815 (Apple version gdb-967) (Tue Jul 14 02:11:58 UTC 2009) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-apple-darwin".sharedlibrary apply-load-rules all Attaching to process 1429. gdb-i386-apple-darwin(1430,0x778720) malloc: * mmap(size=1420296192) failed (error code=12) error: can't allocate region ** set a breakpoint in malloc_error_break to debug gdb stack crawl at point of internal error: [ 0 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (align_down+0x0) [0x1222d8] [ 1 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (xstrvprintf+0x0) [0x12336c] [ 2 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (xmalloc+0x28) [0x12358f] [ 3 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (dyld_info_read_raw_data+0x50) [0x1659af] [ 4 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (dyld_info_read+0x1bc) [0x168a58] [ 5 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (macosx_dyld_update+0xbf) [0x168c9c] [ 6 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (macosx_solib_add+0x36b) [0x169fcc] [ 7 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (macosx_child_attach+0x478) [0x17dd11] [ 8 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (attach_command+0x5d) [0x64ec5] [ 9 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (mi_cmd_target_attach+0x4c) [0x15dbd] [ 10 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (captured_mi_execute_command+0x16d) [0x17427] [ 11 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (catch_exception+0x41) [0x7a99a] [ 12 ] /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/libexec/gdb/gdb-i386-apple-darwin (mi_execute_command+0xa9) [0x16f63] /SourceCache/gdb/gdb-967/src/gdb/utils.c:1144: internal-error: virtual memory exhausted: can't allocate 1420296192 bytes. A problem internal to GDB has been detected, further debugging may prove unreliable.
The Debugger has exited with status 1.The Debugger has exited with status 1.
Here is the backtrace that I get when I set the breakpoint for malloc_error_break:
#0 0x0097a68c in objc_msgSend ()
#1 0x01785bef in -[UILabel setText:] ()
#2 0x000030e0 in -[TableViewController tableView:cellForRowAtIndexPath:] (self=0x421d760, _cmd=0x29cfad8, aTableView=0x4819600, indexPath=0x42190f0) at /Volumes/Main2/Enayet/TableViewController.m:99
#3 0x016cee0c in -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:withIndexPath:] ()
#4 0x016c6a43 in -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:] ()
#5 0x016d954f in -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow] ()
#6 0x016d08ff in -[UITableView layoutSubviews] ()
#7 0x03e672b0 in -[CALayer layoutSublayers] ()
#8 0x03e6706f in CALayerLayoutIfNeeded ()
#9 0x03e668c6 in CA::Context::commit_transaction ()
#10 0x03e6653a in CA::Transaction::commit ()
#11 0x03e6e838 in CA::Transaction::observer_callback ()
#12 0x00b00252 in __CFRunLoopDoObservers ()
#13 0x00aff65f in CFRunLoopRunSpecific ()
#14 0x00afec48 in CFRunLoopRunInMode ()
#15 0x00156615 in GSEventRunModal ()
#16 0x001566da in GSEventRun ()
#17 0x01689faf in UIApplicationMain ()
#18 0x00002398 in main (argc=1, argv=0xbfffefb0) at /Volumes/Main2/Enayet/main.m:14
Here is my complete source for the UITableViewController class:
//
// TableViewController.m
// Enayet
//
// Created by Filip on 12/27/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "TableViewController.h"
#import "EnayetAppDelegate.h"
@implementation TableViewController
@synthesize crayonColors;
@synthesize filteredArray;
@synthesize sectionArray;
@synthesize searchBar;
@synthesize searchDC;
- (TableViewController*) initToCall:(NSMutableDictionary*) toUseArray
{
self = [super initWithStyle:UITableViewStylePlain];
crayonColors = toUseArray;
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
if (aTableView == self.tableView) return 26;
return 1;
}
// Via Jack Lucky
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
[self.searchBar setText:@""];
}
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section
{
if (aTableView == self.tableView)
{
if ([[self.sectionArray objectAtIndex:section] count] == 0)
return nil;
//return [NSString stringWithFormat:@"%@", [[ALPHA substringFromIndex:section] substringToIndex:1]];
return [[ALPHA substringFromIndex:section] substringToIndex:1];
}
else return nil;
}
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
// Normal table
if (aTableView == self.tableView) return [[self.sectionArray objectAtIndex:section] count];
// Search table
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", self.searchBar.text];
self.filteredArray = [self.crayonColors.allKeys filteredArrayUsingPredicate:predicate];
return self.filteredArray.count;
}
// Convert a 6-character hex color to a UIColor object
- (UIColor *) getColor: (NSString *) hexColor
{
unsigned int red, green, blue;
NSRange range;
range.length = 2;
range.location = 0;
[[NSScanner scannerWithString:[hexColor substringWithRange:range]] scanHexInt:&red];
range.location = 2;
[[NSScanner scannerWithString:[hexColor substringWithRange:range]] scanHexInt:&green];
range.location = 4;
[[NSScanner scannerWithString:[hexColor substringWithRange:range]] scanHexInt:&blue];
return [UIColor colorWithRed:(float)(red/255.0f) green:(float)(green/255.0f) blue:(float)(blue/255.0f) alpha:1.0f];
}
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeue or create a cell
UITableViewCellStyle style = UITableViewCellStyleDefault;
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:@"BaseCell"];
if (!cell) cell = [[[UITableViewCell alloc] initWithStyle:style reuseIdentifier:@"BaseCell"] autorelease];
NSString* crayon;
// Retrieve the crayon and its color
if (aTableView == self.tableView)
{
Term *term = (Term *) [[self.sectionArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
crayon = [term name];
}
else
{
crayon = [FILTEREDKEYS objectAtIndex:indexPath.row];
}
cell.textLabel.text = crayon;
if (![crayon hasPrefix:@"White"])
cell.textLabel.textColor = [self.crayonColors objectForKey:crayon];
else
cell.textLabel.textColor = [UIColor blackColor];
return cell;
}
// Grouped Tables do not support section indices (even though they kind of do)
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
if (aTableView == self.tableView) // regular table
{
NSMutableArray *indices = [NSMutableArray arrayWithObject:UITableViewIndexSearch];
for (int i = 0; i < 26; i++)
if ([[self.sectionArray objectAtIndex:i] count])
[indices addObject:[[ALPHA substringFromIndex:i] substringToIndex:1]];
// [indices addObject:@"\ue057"]; // <-- using emoji
return indices;
}
else return nil; // search table
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
if (title == UITableViewIndexSearch) return 0;
return [ALPHA rangeOfString:title].location;
}
- (void)goToTwo:(Term*) toGive
{
EnayetAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate setTerm:toGive];
[appDelegate displayView:2];
}
// Respond to user selections by updating tint colors
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (aTableView == self.tableView)
{
[self goToTwo:[[self.sectionArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]];
}
}
- (void) viewDidLoad
{
// Prepare the crayon color dictionary
NSString *pathname = [[NSBundle mainBundle] pathForResource:@"crayons" ofType:@"txt" inDirectory:@"/"];
NSArray *rawCrayons = [[NSString stringWithContentsOfFile:pathname encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByString:@"\n"];
self.crayonColors = [NSMutableDictionary dictionary];
self.sectionArray = [NSMutableArray array];
for (int i = 0; i < 26; i++) [self.sectionArray addObject:[NSMutableArray array]];
for (NSString *string in rawCrayons)
{
[self.crayonColors setObject:CRAYON_COLOR(string) forKey:CRAYON_NAME(string)];
NSUInteger firstLetter = [ALPHA rangeOfString:[string substringToIndex:1]].location;
if (firstLetter != NSNotFound) [[self.sectionArray objectAtIndex:firstLetter] addObject:[[Term alloc] initToCall:CRAYON_NAME(string)]];
}
//NSLog(@"%@", sectionArray);
// Create a search bar
self.searchBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 44.0f)] autorelease];
self.searchBar.tintColor = COOKBOOK_PURPLE_COLOR;
self.searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.searchBar.keyboardType = UIKeyboardTypeAlphabet;
self.tableView.tableHeaderView = self.searchBar;
// Create the search display controller
self.searchDC = [[[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self] autorelease];
self.searchDC.searchResultsDataSource = self;
self.searchDC.searchResultsDelegate = self;
}
@end
Here is my Term.m file:
//
// Term.m
// Enayet
//
// Created by Filip on 12/27/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "Term.h"
@implementation Term
@synthesize name;
- (Term*)initToCall:(NSString*) toSetName
{
name = toSetName;
return self;
}
- (NSString*)getName
{
return name;
}
-(void)dealloc
{
[name release];
[super dealloc];
}
@end
Upvotes: 1
Views: 1083
Reputation: 78393
In your initToCall:
method, replace:
name = toSetName;
with:
name = [toSetName retain];
Even tho you set the property as a "retain" property, your original value is not retained because you assign it directly. In Cocoa terms, you never "take ownership" of it. Since the string value you were passed in is not owned by you, it ends up going away on you, and that is why you see the crash. If you assign ivars directly in the init methods, you have to remember to retain or copy them yourself. Your other option is to use the accessors:
self.name = toSetName;
In that case, you will automatically invoke your synthesized setter, which will retain the property for you (since you specified it as a "retain" property).
EDIT: Also, while not directly related to your issue here, it's good form to always invoke your superclass's designated initializer from your own designated initializer. So, the general pattern would be:
- (id)initToCall:(NSString*)toSetName
{
if( (self = [super init]) ) { // call NSObject's designated initializer
// do our initialization
name = [toSetName retain]; // take ownership of this string
}
return self;
}
Upvotes: 1
Reputation: 1734
Your getName method is wrong. It needs to return an object with a retain count of +0 (retained and autoreleased in the caller's autorelease context).
-(NSString *)getName
{
return [[name retain] autorelease];
}
As others have said, using @synthesize name to create the -(NSString *)name method and calling that instead is a better idea.
The bug triggering the failure you observed is almost-certainly in the code where name is set; I suspect you have not retained the value correctly. Again if you use @synthesize name and call the generated -(void)setName:(NSString *) method to set the name it will work correctly.
Upvotes: 0
Reputation: 162722
First, if there is a crash, there is a backtrace. Post it. Saw your edit; set a breakpoint on malloc_error_break
and post the backtrace. Usually, such a malloc() failure indicates that you've either exhausted your 32 bit address space or you have caused a bogon value to be passed to malloc.
Secondly, if you have a memory management issue, Instruments can help you track it down. Run your app using the Allocations instrument and try scrolling. It should be quite apparent what is being allocated either so frequently to cause such a crash.
You said:
The problem seems to be the part of where crayon is being set and getName is being called.
If the problem has to do with getName
, then please post the code for getName
. As it is, there isn't anything in that code that seems obviously overly memory consuming.
The getName
method looks fantastically straightforward. :)
In the debugger console, if you type b malloc_error_break
, it'll set the breakpoint. Or you can set the breakpoint as a "symbolic breakpoint" in Xcode's debugger window.
Instruments is a different tool; you can "Run -> Run With Performance Tool -> Allocations" to run your app under instruments.
However, given the entirely bogus value being passed to malloc(), I suspect something else has gone wrong.
That backtrace is actually quite useful. It indicates that the malloc() error is happening when the Cell is trying to allocate something related to setting the text.
And you also said that if you add cell.textLabel.text = @"test";
then the error stops happening.
My best guess; you are either over-releasing or corrupting the string that is supposed to be the rendered as the text of cell.textLabel
.
In particular, I'd bet that crayon
is over-released or corrupt in certain circumstances. Try adding an NSLog(@"crayon %@", crayon);
in your code and see if it barfs, too. And, as someone suggested, try running with Zombie detection enabled (Run -> Run with Performance Tools -> Zombie Detection; or whatever it is called).
Upvotes: 2
Reputation: 162722
Total aside -- not the direct cause of your crash, but indicative of a pattern of use that is alien to standard iOS patterns.
You say you have:
- (NSString*)getName
{
return name;
}
And:
@property (nonatomic, retain) NSString *name;
This is just wrong; there is no need to define a getName
method if you have defined a property for name
. Just use the name
method, either via direct invocation or via dot syntax.
That is, this:
crayon = [[[self.sectionArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] getName];
Should be:
crayon = [[[self.sectionArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] name];
Upvotes: 2