EXC_BAD_ACCESS and stringByAppendingString - ios

Getting EXC_BAD_ACCESS when setting a label with the value returned by the following function defined in a class called DataHelper (all database handling done here):
+(NSString *)getCurrentBalanceAndType:(NSString *)account
{
sqlite3_stmt *statement=NULL;
float bal=0;
NSString *type=NULL, *balance_type=NULL;
//String decimalFormat="%.2f";
statement = [DataHelper getDetailsFromAccountBal:account:[DataHelper currentMonth]];
if (sqlite3_step(statement) == SQLITE_ROW)
{
bal = sqlite3_column_double(statement,2);
type = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
}
sqlite3_finalize(statement);
NSString *stringBal=[NSString stringWithFormat:#"%.2f", bal];
type=[[type uppercaseString] stringByAppendingString:#"r"];
balance_type=[[stringBal stringByAppendingString:#" "]stringByAppendingString:type];
[type release];
return balance_type;
}
This is how I am setting the label in viewDidLoad:
lbCreditCurrBal.text=[DataHelper getCurrentBalanceAndType:#"Some Text"];
Please help.

You are overreleasing type. Remove [type release];. And use ARC to avoid this kind of errors.

When you call a method that begins with anything except init or copy, you’re returned an object that will be autoreleased at some point in the future.
reference- ray's memory management blog
keeping above in mind I think you should not release the type object as said by Nikolai.

Related

va_args() causing EXC_BAD_ACCESS

I'm getting a EXC_BAD_ACCESS when using va_args (iOS 7, Xcode 5.1.1, ARC on):
// ...
int val = sqlIntQuery(#"format_string", #"arg1"); // <-- does not work
int val = sqlIntQuery(#"format_string", #"arg1", nil); // <-- this works
// ...
- (int)sqlIntQuery:(NSString *)format, ...
{
va_list args;
va_start(args,format);
__unsafe_unretained id eachObject;
NSMutableArray *arguments = [NSMutableArray array];
while ( (eachObject = va_arg(args, id)) != nil ) { // <-- crash on 2nd loop
[arguments addObject:eachObject];
}
va_end(args);
// ... process 'arguments'
return 5; // return a computed intValue
}
If I put a "break;" at the end of the loop (because I only have one argument), or add "nil" as a last argument, there's no crash, but I don't think I should have to add the "nil". I suspect an ARC issue, but I'm using __unsafe_unretained, as suggested elsewhere on SO. (Is there a way I can push "nil" into to args?)
What's causing the failure on the second time through the loop?
EDIT Aug 6: My Solution:
The accepted solution by maddy pushed me in the right direction when he mentioned "the number of format specifiers." My format argument has a '?' placeholder for each argument, so I just count those. So, for the record:
- (int)sqlIntQuery:(NSString *)format, ...
{
int numberOfArgs = [format componentsSeparatedByString:#"?"].count - 1; // <<-- this solved my problem
va_list args;
va_start(args,format);
NSMutableArray *arguments = [NSMutableArray array];
while ( numberOfArgs-- ) {
id eachObject = va_arg(args, id);
[arguments addObject:eachObject];
}
va_end(args);
FMResultSet *rs = [db executeQuery:format withArgumentsInArray:arguments];
[rs next];
int ret = [rs intForColumnIndex:0];
[rs close];
return ret;
}
It's a double-wrapper. My routine is a wrapper around FMDB, which is itself a wrapper for SQLite.
You need the nil or some other way to know how many arguments to grab. va_list has no magic way to know when to stop.
Things like stringWithFormat: don't need a nil because it determines the number of arguments based on the number of format specifiers (which is why they need to match or your code goes boom). Note how methods like NSDictionary dictionaryWithObjectsAndKeys: requires a nil terminator or UIAlertView initWithTitle... needs the nil terminator for the otherButtonTitles parameter.
What you could do is make use of the following NSString method:
- (int)sqlIntQuery:(NSString *)format, ... {
va_list args;
va_start(args, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
// do whatever
va_end(args);
return 5;
}
Of course this solutions assumes you wish to build a string from format and the variable arguments to your method.
If you really do need to populate an array then you will need to pass a nil terminator when you call your sqlIntQuery method.

simple calculator app, presents SIGABRT error when I add new button

I'm new to Objective-C programminga nd X-Code so please bear with me. I copied code from a book about objective C to make a calculator class and it's working wonders. Then it asked me as part of the exercises to add a convert button to convert a fraction to a double and print it. The thing is that when I press the convert button, the app stops and I get a message saying that the program received a SIGABRT signal.
Here is the button code:
-(IBAction)clickConvert //convert method
{
if ( [myCalculator accumulator] != 0 ){
displayString = [NSString stringWithFormat: #"%e", [myCalculator convertResult]];
display.text = displayString;
[displayString setString:#""];
}}
The convertResult code:
-(double) convertResult
{
double convertedFraction;
convertedFraction = [accumulator convertToNum];
return convertedFraction;
}
(accumulator is a Fraction object that contains the value of the mathematical operation done on the 2 operands)
and the ConverToNum function:
-(double) convertToNum
{
if (denominator != 0)
return (double) numerator/denominator;
else
return NAN;
}
everything else in teh app works fine, so it must be a problem with what I've done......can anyone help? I've been searching online all day, but I read completely different stuff about this SIGABRT error. Thank you for your help
If displayString is an NSString, then there is no setString setter. Just assign the string to displayString.
Instead of
[displayString setString:#""]
do:
displayString = #"";
Edit:
You say that displayString is an NSMutableString. In that case, your first assignment to displayString:
displayString = [NSString stringWithFormat: #"%e", [myCalculator convertResult]];
assigns an immutable object to the displayString pointer. I don't know why the compiler isn't complaining about that. Then when you try to call setString on displayString, the actual type of the object is NSString even though you have declared the pointer to be an NSMutableString. To fix this, try:
displayString = [[NSString stringWithFormat: #"%e", [myCalculator convertResult]] mutableCopy];
or:
[displayString setString:[NSString stringWithFormat: #"%e", [myCalculator convertResult]]];
or:
displayString = [NSMutableString stringWithFormat: #"%e", [myCalculator convertResult]];

NSMutableArray memory leak when reloading objects

I am using Three20/TTThumbsviewcontroller to load photos. I am struggling since quite a some time now to fix memory leak in setting photosource. I am beginner in Object C & iOS memory management. Please have a look at following code and suggest any obvious mistakes or any errors in declaring and releasing variables.
-- PhotoViewController.h
#interface PhotoViewController : TTThumbsViewController <UIPopoverControllerDelegate,CategoryPickerDelegate,FilterPickerDelegate,UISearchBarDelegate>{
......
NSMutableArray *_photoList;
......
#property(nonatomic,retain) NSMutableArray *photoList;
-- PhotoViewController.m
#implementation PhotoViewController
....
#synthesize photoList;
.....
- (void)LoadPhotoSource:(NSString *)query:(NSString *)title:(NSString* )stoneName{
NSLog(#"log- in loadPhotosource method");
if (photoList == nil)
photoList = [[NSMutableArray alloc] init ];
[photoList removeAllObjects];
#try {
sqlite3 *db;
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [documentsPath stringByAppendingPathComponent: #"DB.s3db"];
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success)
{
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK))
{
NSLog(#"An error has occured.");
}
NSString *_sql = query;
const char *sql = [_sql UTF8String];
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK)
{
NSLog(#"Problem with prepare statement");
}
if ([stoneName length] != 0)
{
NSString *wildcardSearch = [NSString stringWithFormat:#"%#%%",[stoneName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
sqlite3_bind_text(sqlStatement, 1, [wildcardSearch UTF8String], -1, SQLITE_STATIC);
}
while (sqlite3_step(sqlStatement)==SQLITE_ROW) {
NSString* urlSmallImage = #"Mahallati_NoImage.png";
NSString* urlThumbImage = #"Mahallati_NoImage.png";
NSString *designNo = [NSString stringWithUTF8String:(char *) sqlite3_column_text(sqlStatement,2)];
designNo = [designNo stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *desc = [NSString stringWithUTF8String:(char *) sqlite3_column_text(sqlStatement,7)];
desc = [desc stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *caption = designNo;//[designNo stringByAppendingString:desc];
caption = [caption stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *smallFilePath = [documentsPath stringByAppendingPathComponent: [NSString stringWithFormat:#"Small%#.JPG",designNo] ];
smallFilePath = [smallFilePath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([fileMgr fileExistsAtPath:smallFilePath]){
urlSmallImage = [NSString stringWithFormat:#"Small%#.JPG",designNo];
}
NSString *thumbFilePath = [documentsPath stringByAppendingPathComponent: [NSString stringWithFormat:#"Thumb%#.JPG",designNo] ];
thumbFilePath = [thumbFilePath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([fileMgr fileExistsAtPath:thumbFilePath]){
urlThumbImage = [NSString stringWithFormat:#"Thumb%#.JPG",designNo];
}
NSNumber *photoProductId = [NSNumber numberWithInt:(int)sqlite3_column_int(sqlStatement, 0)];
NSNumber *photoPrice = [NSNumber numberWithInt:(int)sqlite3_column_int(sqlStatement, 6)];
char *productNo1 = sqlite3_column_text(sqlStatement, 3);
NSString* productNo;
if (productNo1 == NULL)
productNo = nil;
else
productNo = [NSString stringWithUTF8String:productNo1];
Photo *jphoto = [[[Photo alloc] initWithCaption:caption
urlLarge:[NSString stringWithFormat:#"documents://%#",urlSmallImage]
urlSmall:[NSString stringWithFormat:#"documents://%#",urlSmallImage]
urlThumb:[NSString stringWithFormat:#"documents://%#",urlThumbImage]
size:CGSizeMake(123, 123)
productId:photoProductId
price:photoPrice
description:desc
designNo:designNo
productNo:productNo
] autorelease];
[photoList addObject:jphoto];
[jphoto release];
}
}
#catch (NSException *exception) {
NSLog(#"An exception occured: %#", [exception reason]);
}
self.photoSource = [[[MockPhotoSource alloc]
initWithType:MockPhotoSourceNormal
title:[NSString stringWithFormat: #"%#",title]
photos: photoList
photos2:nil] autorelease];
}
Memory leaks happen when calling above LoadPhotosource method again with different query...
I feel its something wrong in declaring NSMutableArray (photoList), but can't figure out how to fix memory leak.
Any suggestion is really appreciated.
I havent seen any leaked objects in your code, but actually a double released object that will eventually crash your app:
In your last lines, you have this:
Photo *jphoto = [[[Photo alloc] initWithCaption:caption
urlLarge:[NSString stringWithFormat:#"documents://%#",urlSmallImage]
urlSmall:[NSString stringWithFormat:#"documents://%#",urlSmallImage]
urlThumb:[NSString stringWithFormat:#"documents://%#",urlThumbImage]
size:CGSizeMake(123, 123)
productId:photoProductId
price:photoPrice
description:desc
designNo:designNo
productNo:productNo
] autorelease];
[photoList addObject:jphoto];
[jphoto release];
If you take a closer look, you have a double release (autorelease in alloc and release after the addObject). You should remove one of them.
Besides that, I would also recommend you a few things:
Switch to ARC: If you are new, you will definetly take advantage of it because you will not have to worry about almost any of the memory management anymore. Your code will be free of leaked objects and memory crashes. Just be careful with the memory retain cycles and with variable ownerships and you are done.
Remove NSMutableArray *_photoList;, you are not using it since you declare your property syntetizer as #synthesize photoList;. In addition, as Robert commented below, you should use this form to clearly differentiate when you use the property:
#synthesize photoList = photoList_;
The underscore is better at the end because Apple uses it at the beginning and you dont want to accidentally use the same variable name.
Use property accessors to manage ivars. Instead of doing this:
if (photoList == nil)
photoList = [[NSMutableArray alloc] init ];
try this:
if (photoList == nil)
self.photoList = [[[NSMutableArray alloc] init] autorelease];
In your example both lines have the same behaviour, but the second one is preferable because it will rely on your variable memory policy. If you change your retain by a copy or an assign one day, your code will still work without any problem. In the same way, release your memory by assigning your ivar a nil like this self.photoList = nil
Why do you think you have a leaked object?
Just to recap on my observations:
I don't think it's related, but I think you want #synthesize photoList = _photoList. Right now, you have two ivars, the one you declared explicitly, _photoList, and one that is created implicitly from your photoList property, photoList. I'd also get rid of the existing explicit declaration of the _photoList ivar, as Apple current advises that you let the synthesize statement do that for you. (And it prevents situations like this, where you ended up with additional ivars accidentally.)
Also, again unrelated to a leak (but rather the converse problem), but jphoto is being over released, because you're issuing both an autorelease as well as a release. The latter is sufficient.
Also, I don't see your sqlite3_finalize() and sqlite3_close() statements. Where are they? According to the docs, "Every prepared statement must be destroyed using a call to [sqlite3_finalize()] in order to avoid memory leaks."
Finally, if you still have a leak after adding the finalize/close statements for your database, I'd suggest running your code through the leaks tool in the profiler to find the leak. This will help you identify precisely what's leaking.

How to release a string created inside a method?

I have declared a local string inside the method. I am releasing the string inside the same method. I found my code crashing if release that object. If I don't release the string, code runs successfully.
I have called that method in viewdidappear so that method is called while pushing and poping. Nothing gets printed in the console. Here is my code
-(void)appendString{
NSString *locStr = [[NSString alloc] initWithString:#""];
for (int i=0;i<[result count]; i++)
{
locStr=[locStr stringByAppendingFormat:#"%#",[result objectAtIndex:i]];
}
[str setString:locStr];
[locStr release];
}
I am calling the "appendString" method from "viewDidAppear"."str" is a NSMutable string declared in .h class.How should I release the "locStr".
What went wrong in my code? This isn't the normal way to release it?
Try this:
-(void)appendString{
//stringWithString returns an autorelease object
//so you don't need to worry about its memory management
NSString *locStr = [NSString stringWithString:#""];
for (int i=0;i<[result count]; i++)
{
//if your locstr is created by initWithString instead,
//the following line is going to cause memory leak
locStr=[locStr stringByAppendingFormat:#"%#",[result objectAtIndex:i]];
}
[str setString:locStr];
//[locStr release];
}
And make sure that the string property in your str instance is set to retain
You may want to use NSMutableString and not create multiple string objects that may or may not release at some time in the distant future.
-(void)appendString
{
NSMutableString *locStr = [[NSMutableString alloc] initWithString:#""];
for (int i=0;i<[result count]; i++)
{
[locStr appendFormat:#"%#",[result objectAtIndex:i]];
}
[str setString:locStr];
[locStr release];
}
see
In your code, calling [locStr release] is sent to the NSString instance returned from [locStr stringByAppendingFormat:#"%#",[result objectAtIndex:i]] That code actually returns a new String instance, so your variable locStr is now pointing to that new NSString instance and your reference to the original one is lost.
So it is NOT the instance of the NSString you created with [[NSString alloc] initWithString:#""]
The NSString returned from stringByAppendingFormat is autoreleased and your [locStr release] would over-release it. As the other answers indicate you could just use a NSMutableString to avoid lots of NSString instances to be created in your loop and actually releasing the original created instance.
No need to alloc/init locStr. This should do the trick:
-(void)appendString{
NSString *locStr = #"";
for (int i=0;i<[result count]; i++) {
locStr=[locStr stringByAppendingFormat:#"%#",[result objectAtIndex:i]];
}
[str setString:locStr];
}
Don't release, use autorelease. This is the way if you need to return something. When there is an autorelease pool (which is in most cases) it is released automatically. That's what methods like stringByAppendingFormat do also.
Or wait, when you di something like this, it is important, that you retain the Object in the setter Method. It is better then to use a property like
{
NSString* thestring;
}
#property (nonatomic, retain) NSString* thestring;
and a
#synthesize thestring;
in the .m file.
use this
locStr=[[locStr stringByAppendingFormat:#"%#",[result objectAtIndex:i]] retain];
then you can release this same as you are doing.
what happens here you get an autorelease object of string by stringByAppendingFormat method.
so you need to convert it into a retain copy.

Memory management issue with NSString

I have NSMutableArray* cityData that I fill with custom LocationDetail objects. cityData is created in viewDidLoad and released in dealloc.
Somewhere in the code, based on user actions, I populate LocationDetail and add it to cityData array:
LocationDetail* d = [[LocationDetail alloc] init];
d.city = [NSString stringWithFormat:#"%S", (char*)sqlite3_column_text16(statement, 1)];
d.tz = [NSString stringWithFormat:#"%S", (char*)sqlite3_column_text16(statement, 3)];
d.country = [NSString stringWithFormat:#"%S", (char*)sqlite3_column_text16(statement, 2)];
d._id = [NSString stringWithFormat:#"%S", (char*)sqlite3_column_text16(statement, 0)];
[cityData addObject:d];
[d release];
When I am finished with the view controller and remove it, Leaks utility says I have a leak in the code above in NSCFString in all 4 lines with [NSString stringWithFormat] above.
I tried removing the sqlite3 stuff and simplified the call to something like
d._id = [NSString stringWithFormat:#"%s", "a string"]
with the same result. However, if I replace the NSString stringWithFormat like this:
d._id = #"a string";
the leak goes away. I wonder why there is a leak if I use the stringWithFormat, but not if I use #"something". Is there anything obvious that I'm doing wrong?
Thanks!
Properties are not automatically released for you, you need to do that yourself in
- (void)dealloc
See The Objective-C Programming Language: Declared Properties for an example.
Edit:
It seems that the example was moved into the Advanced Memory Management Programming Guide.

Resources