NSString crashing program, how to fix? - iphone

I have the following code:
.h
NSString *mainString;
.m
case 0:
case 1:
case 2:
if ([typeTo textAlignment] == UITextAlignmentRight) {
typeTo.text = [NSString stringWithFormat:#""];
mainString = #"";
[typeTo setTextAlignment:UITextAlignmentLeft];
typeTo.text = [NSString stringWithFormat:#"%#%d", typeTo.text, [sender tag]];
mainString = [NSString stringWithFormat:#"%#%d", mainString, [sender tag]];
} else {
typeTo.text = [NSString stringWithFormat:#"%#%d", typeTo.text, [sender tag]];
mainString = [NSString stringWithFormat:#"%#%d", mainString, [sender tag]];
}
NSLog(#"%#",mainString);
break;
Crashes on this line usually.
mainString = [NSString stringWithFormat:#"%#%d", mainString, [sender tag]];
Code works one then crashes.
both typeTo.text and mainString start as #""
And text alignment starts left.
What am I doing wrong?

If you are not using ARC, then you need to either retain the created string or create it with alloc. So either:
mainString = [[NSString stringWithFormat:#"%#%d", mainString, [sender tag]] retain];
or better yet:
mainString = [[NSString alloc] initWithFormat:#"%#%d", mainString, [sender tag]];
This of course means you also need to release it before assigning a new value.
The reason for the crash is likely because you assign the autorelease instance to the pointer, then the object gets autoreleased but the pointer still points to that now-dead object.
Another way would be to use a property with retain or copy keyword. For strings, copy is usually the better solution because you could accidentally pass a NSMutableString and then later modify it.
Edit to answer comments:
In this case, to avoid a memory leak, the following should be done:
[mainString autorelease];
mainString = [[NSString alloc] initWithFormat:#"%#%d", mainString, [sender tag]];
The reason why this is necessary is because the mainString is used as an argument to create a new object, which is then in turn assigned to mainString. So before the initWithFormat: line, mainString pointed to a string object A. After that line, it now points to a new string object B. But you need to make sure to clean up A, which is why the autorelease is necessary. If you don't you'd have a memory leak and eventually your app will run out of memory.
Alternatively, you could also do:
NSString *tmp = mainString;
mainString = [[NSString alloc] initWithFormat:#"%#%d", tmp, [sender tag]];
[tmp release];
The difference is that autorelease says: I need this object for a short while, but some time after I leave this method it must be cleaned up if possible. release says: I don't need the object any more, please clean it up now if possible.

Related

release in uitableview doesn't work, but autorelease does

In my
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
I am calculating the distance between 2 points, so i have
NSString *sDistance = [[NSString alloc] init];
if (curLat != 0) {
if (curLong != 0) {
double desLat = [[requestedDict objectForKey:#"latitude"] doubleValue];
double desLong = [[requestedDict objectForKey:#"longitude"] doubleValue];
double distance = sqrt(69.1*(desLat-curLat)*69.1*(desLat-curLat)+69.1*(desLong-curLong)*cos(curLat/57.3)*69.1*(desLong-curLong)*cos(curLat/57.3));
sDistance = [NSString stringWithFormat:#"(%.1f mi)",distance];
[[cell distanceLabel] setText:[NSString stringWithFormat:#"(%.1f mi)",distance]];
}
else{
sDistance = #"";
[[cell distanceLabel] setText:#""];
}
}
[sDistance release];
When i do this, i get exc_bad_access errors, but when i change it to
NSString *sDistance = [[[NSString alloc] init] autorelease];
It works just fine. Don't they do the same thing?
sDistance = [NSString stringWithFormat:#"(%.1f mi)",distance];
sDistance = #"";
In both lines sDistanceis pointing to a new string and so you are leaking the alloced string in the first line. When you send a autorelease message then it is added in autorelease pool and thus not leaked later. They are not same. When you alloc then you need to release that. Sending an autorelease message means the object is added to the autorelease pool and will be released later.
You do not need this alloc here as you are creating autoreleased strings later. Just declare the string in first line. And also remove the [sDistance release]; in last line.
NSString *sDistance; // alloc not required
Actually you are not using sDistance anywhere. It does not look like that you need this.

Capture first line of NSString

How do I capture the first line from a NSString object?
I currently am assigning the entire NSString object to the title of my textView, but only want to assign the first line of the string. My current code like this this:
self.textView.text = [[managedObject valueForKey:#"taskText"] description];
You want
self.textView.text = [[[[managedObject valueForKey: #"taskText"] description] componentsSeparatedByString: #"\n"] objectAtIndex:0];
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html
If you’re targeting iOS 4.0 and later, you can use -[NSString enumerateLinesUsingBlock:]:
__block NSString *firstLine = nil;
NSString *wholeText = [[managedObject valueForKey:#"taskText"] description];
[wholeText enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
firstLine = [[line retain] autorelease];
*stop = YES;
}];
self.textView.text = firstLine;
An alternative approach which is probably the most efficient and straightforward:
NSString* str = [[managedObject valueForKey:#"taskText"] description];
self.textView.text = [str substringWithRange:[str lineRangeForRange:NSMakeRange(0, 0)]];

Iphone substring causing memory leak

Im just wrapping up my app, so im onto the stage of running instruments to identify leaks in the app. Ive come across a leak that I cannot work out why it is being registered as a leak.
I have the following lines for example:
NSString *imageType = [[[NSString alloc] initWithString:[loopString substringToIndex:[loopString rangeOfString:#"</IMAGE>"].location]] autorelease];
imageType = [imageType substringFromIndex:[imageType rangeOfString:#"<IMAGE>"].location + :#"<IMAGE>".length];
So basically all im doing is pulling out a section of the "loopstring" and putting that into the imagetype string than just cutting off the trailing fluff of the string using the SubstringFromIndex method.
When I run instruments it says "NSCFString substringwithRange" leak. It highlights the second line:
imageType = [imageType substringFromIndex:[imageType rangeOfString:#"<IMAGE>"].location + :#"<IMAGE>".length];
I would think the substringFromIndex method should return a string that is automatically added to the autorelease pool.
Any ideas on where im going wrong?
Thanks
Following is the refactored code:
- (void)SetupTop10:(NSString *)Top10Data
{
while (Top10Data != #"") {
NSLog(Top10Data);
if ([Top10Data rangeOfString:#"</TOP10ENTRY>"].location == NSNotFound){
Top10Data = #"";
}
else
{
NSString *loopString = [Top10Data substringToIndex:[Top10Data rangeOfString:#"</TOP10ENTRY>"].location + 13];
Top10Data = [Top10Data stringByReplacingOccurrencesOfString:loopString withString:#""];
//NOW CREATE A RECORD FOR THIS ITEM
NSString *imageType = [loopString substringToIndex:[loopString rangeOfString:#"</IMAGE>"].location];
imageType = [imageType substringFromIndex:[imageType rangeOfString:#"<IMAGE>"].location + 7];
NSString *displayText = [loopString substringToIndex:[loopString rangeOfString:#"</DISPLAYTEXT>"].location];
displayText = [displayText substringFromIndex:[displayText rangeOfString:#"<DISPLAYTEXT>"].location + 13];
NSString *link = [loopString substringToIndex:[loopString rangeOfString:#"</INTERESTID>"].location];
link = [link substringFromIndex:[link rangeOfString:#"<INTERESTID>"].location + 12];
[Top10Images addObject:imageType];
[Top10Links addObject:link];
[Top10s addObject:displayText];
Top10RowCount = Top10RowCount + 1;
}
}
[self.Top10Table reloadData];
Top10Table.hidden = NO;
loadingLabel.hidden = YES;
loadingIndicator.hidden = YES;
}
//******************
It doesn't look leaky. But why
NSString *imageType = [[[NSString alloc] initWithString:
[loopString substringToIndex:[loopString
rangeOfString:#"</IMAGE>"].location]
] autorelease];
if you effectively get the same with
NSString *imageType = [loopString substringToIndex:[loopString
rangeOfString:#"</IMAGE>"].location];
with half the memory usage?
Leaks will tell you where the leaked memory was allocated. If you click around (there's a right-arrow icon by the memory address, I think) then you can look at all the allocations/retains/releases for that addresses.
In this example, Leaks will point you to the first line, when it's the fifth one that "leaks" (actually it's a missing release in dealloc/on assignment that leaks):
NSString * s = [someString substringFromIndex:1];
[myArray addObject:s];
// ...
NSString * s2 = [myArray lastObject];
instanceVariable = [s2 retain];
// ... and forget to release in dealloc
What does tableView:cellForRowAtIndexPath: do?
I can't see any problem in the above code. Did you release Top10Images in your dealloc method?

iPhone memory leak pointer

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.

variable parameter function - EXC_BAD_ACCESS when calling [obj release];

I have the following method:
(void)makeString:(NSString *)str1,... {
va_list strings;
NSString *innerText = [[NSString alloc] init];
NSString *tmpStr = [[NSString alloc] init];
if (str1) {
va_start(strings, str1);
while (tmpStr = va_arg(strings, id)) {
innerText = [innerText stringByAppendingString:tmpStr];
}
label.text = [str1 stringByAppendingString:innerText];
}
[tmpStr release];
}
I will eventually get to Objective C Memory Management reading, where I'm sure I will find the answer to this - probably related to pointers and copying - , but for now, can anyone explain why if I add [innerText release]; as the last line of this function, i get an EXC_BAD_ACCESS error at runtime?
First, your code is erroneous.
As far as I can see you are only concatenating the strings to assign the result to label.text.
I assume that label is an ivar, so label.text = … ist legal. Then the following should do it:
- (void)makeString: (NSString *)str1, ...
{
if (str1) {
NSString *tmpStr;
va_list strings;
va_start(strings, str1);
while (tmpStr = va_arg(strings, id)) {
str1 = [str1 stringByAppendingString: tmpStr];
}
label.text = str1;
}
}
Some notes:
You should not release any input parameter unless your method is about releasing something.
As the first answer stated, you should not release the result of stringByAppendingString: unless
you have retained it before.
[Update]
I changed the answer because it contained an error. label.text = str1 should retain str1 of course (if it wants to keep it). Especially the calling code should not retain str1 unless it wants to keep it for itself.
stringByAppendingString returns an autoreleased string, which is replacing your original assignment. So your release is not needed. But you are leaking memory with the two allocs above.
You should probably use [NSString initWithCString:va_arg(strings, id)] to assign the tmpStr too.