How to declare an NSString with multiple possible values - iphone

I want to declare an NSString object to use within an alert, but its actual content depends on various factors, determined by some variable. I'm wondering how best to approach this. In most cases I've done something like this:
- (void)info {
NSString *targetString = [[NSString alloc] init];
switch (self.target) {
case 1:
targetString = #"ONE";
break;
case 2:
targetString = #"TWO";
break;
case 3:
targetString = #"THREE";
break;
default:
targetString = #"";
break;
}
NSString *message = [[NSString alloc] initWithFormat:#"Text: %#", targetString];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Info"
message:message
delegate:self
cancelButtonTitle:#"Ok!"
otherButtonTitles:nil];
alert.tag = kInfoAlert;
[alert show];
[alert release];
[targetString release];
[message release];
}
However when I run this through the build analyser, I get messages telling me the string is leaking memory:
First of all it says:
Value stored to 'targetString' during
its initialization is never read
Then:
Potential leak of an object allocated
on line 137 and stored into
'targetString'
These 2 comments are at line 136 and 137, where line 136 is
NSString *targetString = [[NSString alloc] init];
An alternative might be to declare the string as
NSString *targetString;
and set it in each case as
targetString = [NSString stringWithFormat:#"ONE"];
etc
Or even allocing the String in each case in order to release it at the end...
Well, what would be the best approach here?
Thanks,
Michael :)

The reason for your memory leak is because you are needlessly allocating a string with this line
NSString *targetString = [[NSString alloc] init];
and then setting it to a literal object. Define targetString as nil because when you set it to another value like targetString = #"ONE" you are no longer referencing the empty string you allocated and that causes a memory leak. As for your approach of the switch case for determining the value that is fine.

I believe this would be enough:
NSString *targetString = nil;
And you don't need to release targetString then.

How about this instead of the switch:
- (NSString*) stringForIndex: (NSUInteger) index
{
NSParameterAssert(index < 4);
id strings[] = {#"none", #"one", #"two", #"three"};
return strings[index];
}

Related

Issue with passing variables to FMDB

In my app I'm using FMDB to query my sqlite database. I'm passing variables from one view to another and at the end all the selections the variables are filled with the values selected. On the results page I can show these values in label's fine. But the moment I pass them to FMDB to query the database I don't get any values returned. It crashes and says that the array is 0 which I know it's not.
Code sample below.
- (NSMutableArray *) getChoices
{
NSMutableArray *choices = [[NSMutableArray alloc] init];
FMDatabase *db = [FMDatabase databaseWithPath:[Utility getDatabasePath]];
[db open];
NSString *capsChoiceOne = #"CHOICE 1";
NSString *capsChoiceTwo = #"CHOICE 2";
NSString *capsChoiceThree = #"CHOICE 3";
NSString *capsChoiceFour = #"CHOICE 4";
FMResultSet *results = [db executeQueryWithFormat:#"SELECT * FROM allitems WHERE choice1=%# AND choice2=%# AND choice3=%# AND choice4=%#"
,capsChoiceOne,capsChoiceTwo,capsChoiceThree,capsChoiceFour];
while([results next])
{
Choices *choice = [[Choices alloc] init];
choice.result = [results stringForColumn:#"result"];
[choices addObject:choice];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Your Result"
message:choice.result
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
[db close];
return choices;
Now the above code will bring back the result in the Alert View. But the moment I change one of the values to a variable it crashes and say's there was 0 results in my array. I've put the code below for how I'm inserting the variable.
NSString *capsChoiceOne = self.choiceOneSelected.uppercaseString;
the self.choiceOneSelected.uppercaseString contains the same as the hard coded version but doesn't work.
Any help would be grateful.
Thank you
Check whether self.choiceOneSelected.uppercaseString; is not nil.
Also your query have some issues, you need to wrap string values inside '
So use:
FMResultSet *results = [db executeQueryWithFormat:#"SELECT * FROM allitems WHERE choice1='%#' AND choice2='%#' AND choice3='%#' AND choice4='%#'"
,capsChoiceOne,capsChoiceTwo,capsChoiceThree,capsChoiceFour];

Trying to find leak of type NSMutableArray. Instruments shows leak in method.

I eliminated all the leaks from my current app. However Instruments constantly tells me that I have a leak in the method shown below.
The leak is of type NSMutableArray and has a size of either 16 or 32 bytes. Yes, I know that's not much but it adds up. Also see it as an academic question that I need to solve to make my code leakless.
+ (id) meterFromDict:(NSDictionary*)dict {
Meter* resMeter = [[Meter alloc] initWithType:[[dict objectForKey:#"MeterBase"] intValue]];
//NSLog(#"dict: %#",dict);
resMeter.volume = nil;
resMeter.sounds = nil;
resMeter.repeats = nil;
resMeter.volume = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"volumeArray"]] autorelease];
resMeter.sounds = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"soundsArray"]] autorelease];
resMeter.repeats = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"repeatsArray"]] autorelease];
//NSLog(#"MeterFromDict called and resmeter.repeats count is : %i",[resMeter.repeats count]);
resMeter.bpm = [[dict objectForKey:#"BPM"] floatValue];
return [resMeter autorelease];
}
Without looking at your Instruments output directly I can't tell exactly, but you're writing some redundant code: Try this:
+ (id) meterFromDict:(NSDictionary*)dict {
Meter* resMeter = [[Meter alloc] initWithType:[[dict objectForKey:#"MeterBase"] intValue]];
//NSLog(#"dict: %#",dict);
resMeter.volume = [dict objectForKey:#"volumeArray"];
resMeter.sounds = [dict objectForKey:#"soundsArray"];
resMeter.repeats = [dict objectForKey:#"repeatsArray"];
//NSLog(#"MeterFromDict called and resmeter.repeats count is : %i",[resMeter.repeats count]);
resMeter.bpm = [[dict objectForKey:#"BPM"] floatValue];
return [resMeter autorelease];
}
There's no point in nilling your properties before assigning new values to them.
Also, No point creating new arrays for arrays that you already have. And if you have properly declared your volume, sounds and repeats properties with copy instead of retain.
Try that and see if it works better.

trouble fixing memory leak iphone

i have 3 memory leaks in my application and i don't find how to fix it. I'm kind of new to xcode and objective c. Here is the code i have:
if(sqlite3_prepare_v2( [[DatabaseController sharedDatabaseController] getDb], sqlQueryConverted, -1, &dbStatement, NULL)==SQLITE_OK){
//Run the query
while ( sqlite3_step(dbStatement) == SQLITE_ROW )
{
const char *name = (const char *)sqlite3_column_text(dbStatement, 0);
int courseId = sqlite3_column_int(dbStatement, 1);
const char *location = (const char *)sqlite3_column_text(dbStatement, 2);
const char *date = (const char *)sqlite3_column_text(dbStatement, 3);
//Convert the returnedElement char to string
nameConverted = [[NSString alloc] initWithUTF8String:name];
locationConverted = [[NSString alloc] initWithUTF8String:location];
dateConverted = [[NSString alloc] initWithUTF8String:date];
Course *course = [[[Course alloc]initWithName:nameConverted _id:courseId location:locationConverted courseDate:dateConverted] autorelease];
//Add the course to the to a temporary list to remove duplicated items
[tempResults addObject:course];
}
[nameConverted release];
[locationConverted release];
[dateConverted release];
}
I have tried to autorelease it too. This code is being used to filter a search and reload a search display table. If i put the release line in the while statement the application would crash if i type 2 letters. How could i fix this?
Thanks.
EDIT: I've been going back and forth with this problem with no luck. I've come to the conclusion that there's something wrong with Instruments because it's still showing memory leaks. Here's the code as it is today and as i believe should fix the problem:
NSString *nameConverted = [[NSString alloc] initWithUTF8String:name];
NSString *locationConverted = [[NSString alloc] initWithUTF8String:location];
NSString *dateConverted = [[NSString alloc] initWithUTF8String:date];
Course *course = [[[Course alloc]initWithName:nameConverted _id:courseId location:locationConverted courseDate:dateConverted] autorelease];
//Add the course to the to a temporary list to remove duplicated items
[tempResults addObject:course];
course = nil;
[course release];
[nameConverted release];
nameConverted = nil;
[locationConverted release];
locationConverted = nil;
[dateConverted release];
dateConverted = nil;
NSLog(#"course retain count %i",[course retainCount]);
NSLog(#"name coverted retain count %i",[nameConverted retainCount]);
NSLog(#"location coverted retain count %i",[locationConverted retainCount]);
NSLog(#"date coverted retain count %i",[dateConverted retainCount]);
The logs are telling me that the retainCount = 0; so i don't understand why there's a memory leak. Can you guys give me some advice?
Thanks again.
You're leaking at each loop. You are only releasing the 3 lasts NSString. Everytime you re-assign a new NSString to your 3 variables (nameConverted, locationConverted, dateConverted) you loose the reference to the NSString objects they are pointing too. That means memory leaking. You only release the 3 last ones of them, when you get out of your While loop.
You are creating a memory block each while loop, and then only releasing it once. So if your while loop runs over 10 times, each string has a retain count of 10 but only released once.
To fix, put your 3 releases inside your while loop like this:
if(sqlite3_prepare_v2( [[DatabaseController sharedDatabaseController] getDb], sqlQueryConverted, -1, &dbStatement, NULL)==SQLITE_OK){
//Run the query
while ( sqlite3_step(dbStatement) == SQLITE_ROW )
{
const char *name = (const char *)sqlite3_column_text(dbStatement, 0);
int courseId = sqlite3_column_int(dbStatement, 1);
const char *location = (const char *)sqlite3_column_text(dbStatement, 2);
const char *date = (const char *)sqlite3_column_text(dbStatement, 3);
//Convert the returnedElement char to string
nameConverted = [[NSString alloc] initWithUTF8String:name];
locationConverted = [[NSString alloc] initWithUTF8String:location];
dateConverted = [[NSString alloc] initWithUTF8String:date];
Course *course = [[[Course alloc]initWithName:nameConverted _id:courseId location:locationConverted courseDate:dateConverted] autorelease];
//Add the course to the to a temporary list to remove duplicated items
[tempResults addObject:course];
[nameConverted release];
[locationConverted release];
[dateConverted release];
}
}
Have you tried to use:
nameConverted = [[NSString initWithUTF8String:name] autorelease];
locationConverted = [[NSString initWithUTF8String:location] autorelease];
dateConverted = [[NSString initWithUTF8String:date] autorelease];
And removing the releases outside the loop?
Also do not autoremove the course object, release it after adding it to tempResults.
This is how you should be doing it.
NSString *nameConverted = [[NSString alloc] initWithUTF8String:name];
NSString *locationConverted = [[NSString alloc] initWithUTF8String:location];
NSString *dateConverted = [[NSString alloc] initWithUTF8String:date];
Course *course = [[Course alloc] initWithName:nameConverted _id:courseId location:locationConverted courseDate:dateConverted];
[tempResults addObject:course];
[course release];
[dateConverted release];
[locationConverted release];
[nameConverted release];

NSMutableString append data error

I have AsyncSocket like this.
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
[data getBytes:&tdata];
if (tdata > 5) {
if(bheader){
if(!charS){
if([message isEqualToString:#"S"]){
CMSG = message;
charS=YES;
}
}
else{
NSMutableString *tmp = [[NSMutableString alloc] initWithString:#""];
[tmp appendString:CMSG]; <<<<< This is code error at loop 2,
[tmp appendString:message]; the first loop success but second is fail
CMSG = tmp;
[tmp release];
}
}
else{
if (message){
cmessage = [[NSString alloc]initWithFormat:#"%#%#",cmessage,message] ;
}
else
NSLog(#"Error converting received data into UTF-8 String");
cdata++;
if(cdata==idata) {
msgComplete=YES;
}
}
if (msgComplete) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:cmessage forKey:kNotificationMessage];
[notificationCenter postNotificationName:kNotification object:self userInfo:userInfo];
cmessage=#"";
CMSG=#"";
msgComplete=NO;
bheader=YES;
cdata=0;
charS=NO;
[cmessage release];
}
}
[sock readDataToLength:1 withTimeout:-1 tag:0];
}
This code fail at second loop at [tmp appendString:CMSG]; Thread 1: Program received signal "SIGABRT"
How fix this error ?
Thanks gurus,
CMSG is assigned to tmp but tmp is released right afterwards. CMSG needs to be retained throughout the loop.
CMSG = tmp; //<< should be CMSG = [tmp retain];
[tmp release];
Now you have another potential problem. You are using a deprecated method here
[data getBytes:&tdata];
if (tdata > 5) {
getBytes: was deprecated because of a potential buffer overrun and the result of getBytes is not appropriate to use for if(tdata > 5). If you want to check the length of bytes get that that from the NSData object.
I see an error that may or may not be the ultimate problem here. In the final conditional statement, you have:
cmessage=#"";
...
[cmessage release];
I don't think you should be releasing that string. I don't actually know all of the ins and outs of NSString objects that are defined in that way (as opposed to stringWithFormat: or initWithString:), but I don't think that you have ownership of that string object. You shouldn't have to release it.
Remove that release message and see if it helps.

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?