I have the following code running when the user clicks the "Save" button:
- (IBAction) onSaveChangesClick:(id)sender {
NSMutableString *newGroups = [[NSMutableString alloc] init];
for (int i = 0; i < [self.isInGroupArr count]; i++) {
if ([[self.isInGroupArr objectAtIndex:i] boolValue] == YES) {
[newGroups appendString:[[AppDelegate arrayGroups] objectAtIndex:i]];
[newGroups appendString:#","];
}
}
// remove last : ","
if ([newGroups length] > 0)
newGroups = [NSMutableString stringWithString:[newGroups substringToIndex:[newGroups length] - 1]];
self.contact.groups = newGroups;
[newGroups release];
//[[self navigationController] popViewControllerAnimated:YES];
}
self.IsInGroups is BOOL array and arrayGroups is (NSString *) array that holds groups names.
I would like to add the newGroups string to the arrayGroups[i] only if (IsInGroups[i] == YES).
This piece of code generates EXC_BAD_ACCESS. WHY?
Thanks.
newGroups = [NSMutableString stringWithString:[newGroups substringToIndex:[newGroups length] - 1]];
This line generates a leak, then is the cause of the crash.
After this executes, you no longer have a reference on your alloc/inited mutable string, and you have an autoreleased string. So calling release on that string causes a double release somewhere.
EDIT: with solution
Simplest solution: do not add the last ','.
for (int i = 0; i < [self.isInGroupArr count]; i++) {
if ([[self.isInGroupArr objectAtIndex:i] boolValue] == YES) {
[newGroups appendString:[[AppDelegate arrayGroups] objectAtIndex:i]];
if (i != ([self.isInGroupArr count] - 1))
[newGroups appendString:#","];
}
}
Not very elegant, but quite efficient (could avoid taking the count each time though).
Here you create autoreleased instance of NSMutable string.
newGroups = [NSMutableString stringWithString:[newGroups substringToIndex:[newGroups length] - 1]];
so you shouldn't release it, and all will be fine.
Here is improved code:
- (IBAction) onSaveChangesClick:(id)sender {
NSMutableString *newGroups = [[[NSMutableString alloc] init] autorelease];
for (int i = 0; i < [self.isInGroupArr count]; i++) {
if ([[self.isInGroupArr objectAtIndex:i] boolValue] == YES) {
[newGroups appendString:[[AppDelegate arrayGroups] objectAtIndex:i]];
[newGroups appendString:#","];
}
}
// remove last : ","
if ([newGroups length] > 0)
newGroups = [NSMutableString stringWithString:[newGroups substringToIndex:[newGroups length] - 1]];
self.contact.groups = newGroups;
//[[self navigationController] popViewControllerAnimated:YES];
}
Explanation:
Here you allocate memory and retain it.
[[NSMutableString alloc] init]
[NSMutableString stringWithString: returns autoreleased instance of NSMutable string, that we shouldn't release(it does the same as [[[NSMutableString alloc] init] autorelease] + smth more). and you assign it to variable newGroups(so your old value that was stored in this variable lost)
if ([newGroups length] > 0)
newGroups = [NSMutableString stringWithString:[newGroups substringToIndex:[newGroups length] - 1]];
newGroups here is autoreleased, you release it, and it destroys. But as it was autoreleased, autorelease pool tries to release it again and gets exception(because memory is allready free)
[newGroups release];
You have allocated a string(NSMutableString *newGroups = [[NSMutableString alloc] init];
)
and then assigning it with an autorelease string(newGroups = [NSMutableString stringWithString:[newGroups substringToIndex:[newGroups length] - 1]];
).
Which you should never do. See this blog - http://andycodes.tumblr.com/post/947927280/difficult-bug-finally-solved
Comment out [newGroups release]; and code should work fine.
Also always set NSZombieEnabled environment variable and run the code again, have a look at the crash log, you will get exactly which object is causing the crash.
Related
I am getting memory leaks in Instruments in the following Sqlite Code.
NSArray *result = [self executeQuery:sql arguments:argsArray];
It calls following method.
- (NSArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return nil;
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
NSMutableArray *arrayList = [[NSMutableArray alloc] init];
int columnCount = sqlite3_column_count(sqlStmt);
while ([self hasData:sqlStmt]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
for (i = 0; i < columnCount; ++i) {
id columnName = [self columnName:sqlStmt columnIndex:i];
id columnData = [self columnData:sqlStmt columnIndex:i];
[dictionary setObject:columnData forKey:columnName];
}
[arrayList addObject:[dictionary autorelease]];
}
sqlite3_finalize(sqlStmt);
return arrayList;
}
How do I solve it ?
We'd need to see the code of your executeQuery method - it should be returning an auto-released result, but perhaps it isn't.
You could try ;
NSArray *result = [[self executeQuery:sql arguments:argsArray] autorelease];
But I'd be wary of just blindly trying that without actually seeing what executeQuery does in detail.
EDIT:
OK, here's your problem;
NSMutableArray *arrayList = [[NSMutableArray alloc] init];
Either create it as an auto-released array, or finish the method with;
return [arrayList autorelease];
Been running instruments on my app. Its says i am leaking 864bytes & 624bytes from 2 NSCFString and the library responsible is Foundation.
So that leads me to believe thats its not a leak caused by me? Or is it?
Here is the offending method according to instruments. It seems to be a
substringWithRange
that is leaking.
-(void) loadDeckData
{
deckArray =[[NSMutableArray alloc] init];
NSString* path = [[NSBundle mainBundle] pathForResource:#"rugby" ofType:#"txt"
inDirectory:#""];
NSString* data = [NSString stringWithContentsOfFile:path encoding:
NSUTF8StringEncoding error: NULL];
NSString *newString = #"";
NSString *newline = #"\n";
NSString *comma = #",";
int commaCount = 0;
int rangeCount = 0;
NSString *nameHolder = #"";
NSString *infoHolder = #"";
NSMutableArray *statsHolder = [[NSMutableArray alloc] init];
for (int i=0; i<data.length; i++)
{
newString = [data substringWithRange:NSMakeRange(i, 1)];
if ([newString isEqualToString: comma]) //if we find a comma
{
if (commaCount == 0)// if it was the first comma we are parsing the
NAME
{
nameHolder = [data substringWithRange:NSMakeRange(i-
rangeCount, rangeCount)];
}
else if (commaCount == 1)//
{
infoHolder = [data substringWithRange:NSMakeRange(i-
rangeCount, rangeCount)];
//NSLog(infoHolder);
}
else // if we are on to 2nd,3rd,nth comma we are parsing stats
{
NSInteger theValue = [[data
substringWithRange:NSMakeRange(i-rangeCount,rangeCount)]
integerValue];
NSNumber* boxedValue = [NSNumber
numberWithInteger:theValue];
[statsHolder addObject:boxedValue];
}
rangeCount=0;
commaCount++;
}
else if ([newString isEqualToString: newline])
{
NSInteger theValue = [[data substringWithRange:NSMakeRange(i-
rangeCount,rangeCount)] integerValue];
NSNumber* boxedValue = [NSNumber numberWithInteger:theValue];
[statsHolder addObject:boxedValue];
commaCount=0;
rangeCount=0;
Card *myCard = [[Card alloc] init];
myCard.name = nameHolder;
myCard.information = infoHolder;
for (int x = 0; x < [statsHolder count]; x++)
{
[myCard.statsArray addObject:[statsHolder
objectAtIndex:x]];
}
[deckArray addObject:myCard];
[myCard autorelease];
[statsHolder removeAllObjects];
}
else
{
rangeCount++;
}
}
[statsHolder autorelease];
}
Thanks for your advice.
-Code
As Gary's comment suggests this is very difficult to diagnose based on your question.
It's almost certainly a leak caused by you however, I'm afraid.
If you go to the View menu you can open the Extended Detail. This should allow you to view a stack trace of exactly where the leak occurred. This should help diagnose the problem.
When to release deckArray? If deckArray is a class member variable and not nil, should it be released before allocate and initialize memory space?
another memory management question:
I have asked this before, but did not really get an answer:
The question is would the following result in a leak or is it ok?
NSArray *txtArray = [NSArray array];
NSString *aTxtFieldTxt = [[NSString alloc]initWithString:aTxtField.text];
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSMutableString *aTxt = [[NSMutableString alloc]initWithString:aTxtFieldTxt];
[aTxtFieldTxt release];
txtArray = [aTxt componentsSeparatedByString:#" "];
aTxt = [[txtArray objectAtIndex:0] retain];
for(int i = 1; i < [txtArray count]; i++){
[aTxt appendString:#"+"];
[aTxt appendString:[[txtArray objectAtIndex:i]retain]];
}
This is part of a function. And I am not sure if the assignment of aTxt = [[txtArray objectAtIndex:0] retain]; causes a leak because it is a pointer which originally points to
NSMutableString *aTxt = [[NSMutableString alloc]initWithString:aTxtFieldTxt];
[aTxtFieldTxt release];
How do I do this correctly. Would I have to use another pointer? Can somebody please explain this issue?
Thanks alot!
Lots and lots of issues with this code.
//
// Don't do this. Just declare the txtArray
//
NSArray *txtArray /* = [NSArray array]*/;
//
// You need to auto release after init in this case.
//
NSString *aTxtFieldTxt = [[[NSString alloc]initWithString:[aTxtField text]] autorelease];
//
// You are reassigning the aTxtFieldTxt and the new value is returned autoreleased.
//
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//
// Again, autorelease after init
//
NSMutableString *aTxt = [[[NSMutableString alloc]initWithString:aTxtFieldTxt] autorelease];
//
// You never alloced this instance so it needs no release.
//
/*[aTxtFieldTxt release]; */
//
// This array is returned autoreleased
//
txtArray = [aTxt componentsSeparatedByString:#" "];
//
// No need to retain here. Just get the object
//
aTxt = /*[*/[txtArray objectAtIndex:0]/* retain]*/;
for(int i = 1; i < [txtArray count]; i++)
{
[aTxt appendString:#"+"];
[aTxt appendString:[[txtArray objectAtIndex:i]retain]];
}
I have found, that if you have retains/releases outside of accessors/base int/dealloc routines, you are doing something wrong. For every alloc you must have a balanced release/retain for that instance of the object. If you reassign the variable, you will loose your reference to it.
This is a quick stab on how I would write this code:
NSArray *txtArray;
NSString *aTxtFieldTxt = [NSString stringWithString:[aTxtField text]];
NSMutableString *aTxt;
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
aTxt = [NSMutableString stringWithString:aTxtFieldTxt];
txtArray = [aTxt componentsSeparatedByString:#" "];
aTxt = [NSMutableString stringWithString:[txtArray objectAtIndex:0]];
for(int i = 1; i < [txtArray count]; i++)
{
[aTxt appendString:#"+"];
[aTxt appendString:[[txtArray objectAtIndex:i]retain]];
}
Try running your application with Leaks. See if it causes a leak. Leaks is a tool in Instruments.
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
This will cause the original reference to aTxtFieldTxt being lost (thus a leak). Even worse, a convenient function will return an object of retain count 0, so the -release later will crash the program.
It's better written as
// don't stuff everything into a single line.
NSCharacterSet* charsToTrim = [NSCharacterSet whitespaceAndNewlineCharacterSet];
// there's no need to create a copy of the string.
NSString* aTxtFieldTxt = [aTxtField.text stringByTrimmingCharactersInSet:charsToTrim];
// don't release stuff with retain count 0.
// you just want to replace all ' ' by '+' right? there's a method for that.
NSString* aTxt = [aTxtFieldTxt stringByReplacingOccurrencesOfString:#" "
withString:#"+"];
Edit: If you need to replace multiple spaces to one +, you need to parse the string, e.g.
NSCharacterSet* charsToTrim = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString* aTxtFieldTxt = [aTxtField.text stringByTrimmingCharactersInSet:charsToTrim];
NSScanner* scanner = [NSScanner scannerWithString:aTxtFieldTxt];
[scanner setCharactersToBeSkipped:nil];
NSMutableString* aTxt = [NSMutableString string];
NSCharacterSet* whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
while (![scanner isAtEnd]) {
NSString* res;
[scanner scanUpToCharactersFromSet:whitespaceSet intoString:&res];
[aTxt appendString:res];
if ([scanner scanCharactersFromSet:whitespaceSet intoString:NULL])
[aTxt appendString:#"+"];
}
As others pointed out, this code leaks all over the place.
A real good way to find those without even running your code is the static analyzer!
Use Build and Analyze from the Build menu, and it will show you what leaks and why, with complete code paths.
In my iPhone Project when i select build and analyze (shift + mac + A ) it will give me all potential memory leak in my project... but in my current project it is not working... when i intentionally put a memory leak and select build and analyze... it doesn't give me any potential memory leak as analyzer result
if i write
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithCapacity:6];
NSMutableArray *tempArrayfinal = [[NSMutableArray alloc] initWithCapacity:12];
and doesn't release it... it is not giving me any potential leak but if i write
NSString *leakyString = [[NSString alloc] initWithString:#"Leaky String "];
NSLog(#"%#",leakyString);
here it gives me potential leak as analyzer result...why it is not giving potential leak in NSMutableArray and why it gives potential leak in NSString ?... how can i rely on Build and analyze for memory leak...
I've run the app with Leak tool and it is showing me leak in tempArray and tempArrayfinal
Here is my function
- (void)viewDidLoad
{
[super viewDidLoad];
maxOnSnaxAppDelegate *delegate = (maxOnSnaxAppDelegate *)[[UIApplication sharedApplication] delegate];
lblMessage.tag = MESSAGE_LABEL;
lblGuess.tag = GUESS_LABEL;
lblScore.tag = SCORE_LABEL;
self.gameCompleteView.tag = FINAL_SCORE_VIEW;
lblFinalScore.tag = FINAL_SCORE_LABEL;
lblGuess.text = #"";
lblMessage.text = #"";
lblScore.text = #"";
int row = 0;
int column = 0;
maxImagrArray = [[NSMutableArray alloc] init];
for(UIView *subview in [self.view subviews]) {
if([subview isKindOfClass:[CustomImageView class]])
{
[subview removeFromSuperview];
}
}
for(int i = 0 ; i < 12 ; i++)
{
if(i%3 == 0 && i != 0)
{
row++;
column = -1;
}
if(i != 0 )
{
column++;
//row = 0;
}
CustomImageView *tempImageView = [[CustomImageView alloc] initWithImage:[UIImage imageNamed:#"max-img.png"]];
tempImageView.frame = CGRectMake((column*tempImageView.frame.size.width) + 45, (row*tempImageView.frame.size.height)+ 60, tempImageView.frame.size.width, tempImageView.frame.size.height);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, tempImageView.bounds);
[self.view addSubview:tempImageView];
tempImageView.tag = i+1;
tempImageView.userInteractionEnabled = YES;
[maxImagrArray addObject:tempImageView];
[tempImageView setIndex:i];
[tempImageView release];
}
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithCapacity:6];
NSMutableArray *tempArrayfinal = [[NSMutableArray alloc] initWithCapacity:12];
for(int i = 0 ; i < 12 ; i++)
{
if(i < 6)
{
int temp = (arc4random() % 10) + 1;
NSString *tempStr = [[NSString alloc] initWithFormat:#"%d",temp];
[tempArray insertObject:tempStr atIndex:i];
[tempArrayfinal insertObject:tempStr atIndex:i];
[tempStr release];
}
else
{
int temp = (arc4random() % [tempArray count]);
[tempArrayfinal insertObject: (NSString *)[tempArray objectAtIndex:temp] atIndex:i];
//int index = [(NSString *)[tempArray objectAtIndex:temp] intValue];
[tempArray removeObjectAtIndex:temp];
}
CustomImageView *tmpCustom = [maxImagrArray objectAtIndex:i];
tmpCustom.frontImageIndex = [(NSString *)[tempArrayfinal objectAtIndex:i] intValue];
NSLog(#"%d",tmpCustom.frontImageIndex);
}
[maxImagrArray release];
delegate.time = 60.0;
timer = nil;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countDown) userInfo:nil repeats:YES];
delegate.view = self.view;
//[tempArray release];
//[tempArrayfinal release];//these 2 lines are deliberately commented to see
//...it is not showing any potential memory leak though....
delegate.viewController = self;
}
please help...
[tempArray insertObject:tempStr atIndex:i];
[tempArrayfinal insertObject:tempStr atIndex:i];
are the culprits... when i uncomment these 2 lines.. it stops giving me the warning... i don't know why...
You shouldn't really rely on the Build and Analyze tool. It does not always catch all leaks. Threre is no substitute for following the memory management rules from apple (apart from garbage collection on OS X). However, I'm not sure why it isn't catching such a simple leak. I tried at line in a method and it did give me a result. Are you sure its not in an if statement, because I've found that sometimes in an if statement it won't correct you.
In Instruments the tempPlayer object is showing a leak. In this code, in every for loop I keep on allocating a new tempPlayer instance, set its playerCode variable with a string and add it to an NSMutableArray in each iteration. Instruments shows me a leak in the alloc statement. Is there any way to prevent tat leak in the tempPlayer object ?
for(int i = 0 ; i < [homeLineupArray count] ; i++) {
NSArray * tildeSeperator = [[homeLineupArray objectAtIndex:i] componentsSeparatedByString:#"~"];
[self.tempPlayer release];
self.tempPlayer = [[LineUpsPlayer alloc] init];
tempPlayer.playerCode = [tildeSeperator objectAtIndex:0];
[matchLineUp.homeTeamPlayingEleven addObject:tempPlayer ];
}
Thanks
Harikant Jammi
I would simply do this.
for(int i = 0 ; i < [homeLineupArray count] ; i++) {
NSArray *tildeSeperator = [[homeLineupArray objectAtIndex:i] componentsSeparatedByString:#"~"];
LineUpsPLayer *player = [[[LineUpsPlayer alloc] init] autorelease];
player.playerCode = [tildeSeperator objectAtIndex:0];
[matchLineUp.homeTeamPlayingEleven addObject:player];
}
You can also replace your loop with this:
for (NSString *lineup in homeLineupArray) {
NSArray *tildeSeparator = [lineup componentsSeparatedByString:#"~"];
...
}
You don't usually want to save each item while iterating through an array to an instance variable since it keeps changing and you only reference it in the method.
Rather than using the property, tempPlayer, use a local variable, and release after adding to the array:
for(int i = 0 ; i < [homeLineupArray count] ; i++) {
NSArray * tildeSeperator = [[homeLineupArray objectAtIndex:i] componentsSeparatedByString:#"~"];
LineUpsPlayer* tempPlayer = [[LineUpsPlayer alloc] init];
tempPlayer.playerCode = [tildeSeperator objectAtIndex:0];
[matchLineUp.homeTeamPlayingEleven addObject:tempPlayer ];
[tempPlayer release];
}
The array retains the objects you add - that's why you need to release the temporary object too.
Things may depend on how do you declare your tempPlayer property in your class (and as it looks that it is temporary object, consider do you need a property accessor for it?)
for(int i = 0 ; i < [homeLineupArray count] ; i++) {
NSArray * tildeSeperator = [[homeLineupArray objectAtIndex:i] componentsSeparatedByString:#"~"];
[self.tempPlayer release]; // Decrease retain count
self.tempPlayer = [[LineUpsPlayer alloc] init]; // retain count increase by 1 or 2
tempPlayer.playerCode = [tildeSeperator objectAtIndex:0];
[matchLineUp.homeTeamPlayingEleven addObject:tempPlayer]; // retain count increase
}
So as you can see you retain your object more times then you release it so it eventually leaks. Probably your code may be rewritten this way:
for(int i = 0 ; i < [homeLineupArray count] ; i++) {
NSArray * tildeSeperator = [[homeLineupArray objectAtIndex:i] componentsSeparatedByString:#"~"];
LineUpsPlayer* tempPlayer = [[LineUpsPlayer alloc] init]; // object's retain count is 1
tempPlayer.playerCode = [tildeSeperator objectAtIndex:0];
[matchLineUp.homeTeamPlayingEleven addObject:tempPlayer]; // container takes ownership of the object
[tempPlayer release]; // we do not need to own this object as it is in container now
}
As said above, I normally use a local variable to populate the mutable array. In other cases Instruments didnt show me any leak. So is this the right way to go about ?
for(int i =0 ; i < ([arrayOfHomePlayers count]); i++ ) //creating home players detials object
{
localString = [NSMutableString string];
localString = [arrayOfHomePlayers objectAtIndex:i] ;
NSArray * localPlayersArray = [localString componentsSeparatedByString:#"~"] ;
localPlayerPosition = [ [PlayerPosition alloc] init] ;
NSArray * playerNameArray = [[localPlayersArray objectAtIndex:0] componentsSeparatedByString:#" "] ;
localPlayerPosition.globalID = [localPlayersArray objectAtIndex:1];
[(teamFormationDetails.homePlayerList) addObject:localPlayerPosition] ;
[localPlayerPosition release] ;
First, you could change the release to an autorelease and move it to the line after the self is assigned.
Second, the addobject is adding it to a collection which retains it, and I don't see you removing it from that collection, so that's your 'leak'. However, I'm not sure there even is a leak, if you intended to leave it in the collection.