I'm stuck with the following bit of code.
NSString *gridRef = [[NSString alloc] initWithFormat: #"%#", [converter LatLongToOSGrid: latLong]];
NSLog(#"Grid Ref: %#", gridRef);
self.answerLabel.text = [[NSString alloc] initWithFormat: #"%#", gridRef];
When I log gridRef, it displays the correct result. However, the line setting answerLabel.text causes an EXC_BAD_ACCESS error and the program crashes. IB is connected to the correct label, what is the problem?
Thanks
I've updated the code as follows:
- (IBAction)convertLatLong {
NSArray *latLong = [[NSArray alloc] initWithObjects: latTextField.text, longTextField.text, nil];
GridRefsConverter *converter = [[GridRefsConverter alloc] init];
NSString *gridRef = [[NSString alloc] initWithFormat: #"%#", [converter LatLongToOSGrid: latLong]];
NSLog(#"Grid Ref: %#", gridRef);
NSLog(#"Label: %#", self.answerLabel.text);
answerLabel.text = #"Yippy";
self.answerLabel.text = gridRef;
[gridRef release];
[converter release];
[latLong release];
}
answerLabel is initialised through #property #synthesize when the view controller is pushed onto the stack. (I don't know how it gets init'd apart from it's one of the magical things IB does for you. Or so I assume. I've used exactly the same method in other view controllers and have not had this issue.
I've found the culprits - the question is, how do I go about releasing them?
NSString *eString = [[NSString alloc] initWithFormat: #"%f", e];
NSString *nString = [[NSString alloc] initWithFormat: #"%f", n];
eString = [eString stringByPaddingToLength: (digits/2) withString: #"0" startingAtIndex: 0];
nString = [nString stringByPaddingToLength: (digits/2) withString: #"0" startingAtIndex: 0];
NSString *theGridRef = [letterPair stringByAppendingString: eString];
theGridRef = [theGridRef stringByAppendingString: nString];
[eString release];
[nString release];
return theGridRef;
and:
NSArray *gridRef = [[NSArray alloc] init];
gridRef = [gridRef arrayByAddingObject: [NSNumber numberWithDouble: E]];
gridRef = [gridRef arrayByAddingObject: [NSNumber numberWithDouble: N]];
gridRef = [gridRef arrayByAddingObject: [NSNumber numberWithInteger: 8]];
NSString *theGridRef = [[NSString alloc] initWithFormat: #"%#", [self gridRefNumberToLetter: gridRef]];
[gridRef release];
[theGridRef autorelease];
return theGridRef;
}
You should enable zombie detection by setting the environment variable NSZombieEnabled to YES, so you can see which object causes the bad access (don't forget to remove this again when you found the bug).
Also you can use Instruments to find the location where the object actually gets released. For this start a new Instruments session and use the "Allocations" instrument. In the instrument settings check "Enable NSZombie detection" and "Record reference counts". When running the session you will break where the error occurs and you see a record of all retains/releases.
One place where you can have a quick look if your object is incorrectly freed is in the -viewDidUnload method, where you should release the outlet and set it to nil. If you forget the latter and you access the outlet somehow, it will result in a EXC_BAD_ACCESS.
Edited to match your update:
The problem is that you are assigning eString (and nString) a new string which was alloc/init-ed. Then you override those in the next statements, because -stringByPaddingToLength: (as well as all the other -stringBy... methods) return a new and autoreleased string object. So you lost the reference to the old string which means that there is a memory leak. Additionally at the end you release the already autoreleased objects explicitly which causes your bad access.
Instead you should create autoreleased strings from the beginning ([NSString stringWithFormat:...]) and don't release them at the end.
Check if asnwerLabel is actually non-null. You should also change this line:
self.answerLabel.text = [[NSString alloc] initWithFormat: #"%#", gridRef];
To:
self.answerLabel.text = [NSString stringWithFormat: #"%#", gridRef];
Otherwise, you will end up with a memory leak in that line.
Maybe the label is not inited at that point in your code, try to check it. Why are you allocating a new NSString?
Just do:
self.label.text = gridRef;
[gridRef release];
how is answerLabel created? You might need to retain that. Or you possibly need to release some things (gridRef)?
I can't see any other issues with your code.
You can (and probably should) set your
answerLabel.text = gridRef;
gridRef is already an NSString, so you don't need to alloc it again.
EXC_BAD_ACCESS is usually a memory thing related to your retain/release count not balancing (or in my extensive experience of it :p).
Okay, the problem was trying to release NSStrings, so I've stopped doing that and the problem has been solved.
Can someone clarify how strings are retained and released. I was of the impression that:
string = #"My String"; is autoreleased.
NSString *string = [[NSString alloc] init...]; is not autoreleased and needs to be done manually.
Related
hope someone can help me with a problem I've been wrestling with...
Using MapBox to develop a map-based app, and I want to attach an NSMutableDictionary to each of the map annotations to store additional data. I had it working but XCode kept throwing me warning about some of my data/object types, so I went through and tidied those up, and now it's broken. The idea is that on ViewDidLoad, the program runs through a set of plist dictionaries to set up each annotation correctly - that's still running okay, because my initial anno markers pop up with their correct settings. However rather than run back to the plist every time, I want to attach a dictionary to each annotation's userinfo property, which I can then use for toggling selection data and other functions. Here's my code:
NSDictionary *ExploreSteps = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ExploreSteps" ofType:#"plist"]];
for (NSString *key in [ExploreSteps allKeys])
{
//Loop through keys for each anno
NSDictionary *thisStep = [ExploreSteps objectForKey:key];
NSNumber *annoIndex = [thisStep objectForKey:#"Index"];
NSNumber *isLive = [thisStep valueForKey:#"isLive"];
NSString *annoTitle = [thisStep objectForKey:#"Title"];
NSString *annoText = [thisStep objectForKey:#"Text"];
NSString *imagefile = [thisStep objectForKey:#"Imagefile"];
double longitude = [[thisStep objectForKey:#"Longitude"] doubleValue];
double latitude = [[thisStep objectForKey:#"Latitude"] doubleValue];
NSString *pagefile = [thisStep objectForKey:#"Pagefile"];
NSString *audiofile = [thisStep objectForKey:#"Audiofile"];
CLLocationCoordinate2D annoCoord = CLLocationCoordinate2DMake(latitude, longitude);
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:mapView coordinate:annoCoord andTitle:annoTitle];
annotation.annotationIcon = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:imagefile ofType:#"png"]];
annotation.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:annoIndex, #"index", isLive, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
NSLog(#"Title: %#",[annotation.userInfo objectForKey:#"annoTitle"]);
[mapView addAnnotation:annotation];
}
The NSLog should spit out the annoTitle string, but instead it's giving me a null every time, and the behaviour of the rest of the app also shows that info stored in the dictionary simply isn't "getting through".
Any ideas? Thanks in advance!
ETA: Modified code for initializing the dictionary (not that it seems to make any difference to the problem!):
NSMutableDictionary *myUserInfo = [[NSMutableDictionary alloc] initWithObjectsAndKeys:annoIndex, #"index", isLive, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
annotation.userInfo = myUserInfo;
NSLog(#"Title: %#",[annotation.userInfo objectForKey:#"annoTitle"]);
NSLog(#"Length: %u",[[annotation.userInfo allKeys] count]);
(Title now returns "(null)", while Length returns "1", if that's at all helpful...)
Almost certainly one of your objects is nil. You mention that allKeys] count] returns 1 so I can go further and say that your value for isLive is nil. Hence your original line:
[NSMutableDictionary dictionaryWithObjectsAndKeys:annoIndex, #"index", isLive, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
Acts exactly the same as:
[NSMutableDictionary dictionaryWithObjectsAndKeys:annoIndex, #"index", nil, #"isLive", annoTitle, #"annoTitle", annoText, #"annoText", imagefile, #"imagefile", pagefile, #"pagefile", audiofile, #"audiofile", nil];
And the dictionary takes annoIndex to be the final key-value pair.
I'd suggest that probably you want to take a mutable copy of thisStep and strip out the keys you don't want, then pass it along as the userInfo.
It's the way you are creating the NSMutableDictionary for userInfo. Take a look at this Difference between [[NSMutableDictionary alloc] initWithObjects:...] and [NSMutableDictionary dictionaryWithObjects:...]?
"
+dictionaryWithObjects: returns an autoreleased dictionary
-initWithObjects: you must release yourself
if you want the dictionary to persist as a instance variable, you should create it with an init method or retain an autoreleased version, either way you should be sure to release it in your dealloc method
"
I am trying to return an NSString that has been initialized from a plist.
If I comment out my release lines this code works. I would however like to release these objects from memory as I no longer need them.
I thought that 'initWithString' would copy the contents of the target string into my new string meaning I could safely release the NSMutableArray. But it isn't. Why not?
+ (NSString*) genImage {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"Images" ofType:#"plist"];
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSLog(#"%d", [arrayOfImages count]);
int indexToLoad = 0;
NSString *res = [[NSString alloc] initWithString:[arrayOfImages objectAtIndex:indexToLoad] ];
[arrayOfImages release];
[path release];
return res;
}
You do not retain the return value of -[NSBundle pathForResource:ofType:] (the path variable), so there is no need to release it (and doing so will cause a crash, most likely). However, you should autorelease res, as you do retain that. You can change your last line to
return [res autorelease];
The basic structure of my program has the user select an item from a UITableView, which corresponds to a stored text file. The file is then read into an array and a dictionary, where the array has the keys (I know I can just get the keys from the dictionary itself, this isn't my question).
The view is then changed to a UISplitView where the master view has the keys, and the detail view has the items in the dictionary attributed to that key. In this case, it's a series of "Yes/No" questions that the user selects the answer to.
My problem is this: When I click on a cell in the UITableView (first screen), it works fine, the data is read in perfectly, and so on. When I go back to the UITableView and click on the same cell again, the program crashes. Here is the read-in-from-file method:
-(NSArray *)readFromFile:(NSString *)filePath{
// NSLog(#"Path was: %#", filePath);
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
// NSLog(#"File was: %#", file);
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
/**
This is where the fun stuff happens!
**/
while(![fileScanner isAtEnd]){
//Scan the string into held
[fileScanner scanUpToString:#"\r" intoString:&held];
NSLog(#"Inside the while loop");
// If it is a character, it's one of the Key points, so we do the management necessary
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 2: %#", [checkers objectAtIndex:2]);
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
}
NSLog(#"After if statement");
key = [checkers objectAtIndex:2];
[keys addObject:(NSString *) key];
detailStrings = [[NSMutableArray alloc] init];
}
else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 1: %#", [checkers objectAtIndex:1]);
[detailStrings addObject:[checkers objectAtIndex:1]];
}
}
NSLog(#"File has been read in");
[details setObject:detailStrings forKey:key];
NSArray *contents = [[NSArray alloc] initWithObjects:(NSMutableArray *) keys, (NSMutableDictionary *) details, nil];
[detailStrings release];
return contents;
}
I've determined that the program crashes inside the
if(detailStrings != nil)
statement. I figure this is because I'm missing some memory management that I am supposed to be doing, but don't have the knowledge of where it's going wrong. Any ideas as to the problem, or why it is crashing without giving me a log?
detailStrings is not initialized when you enter the while loop. When you declare NSMutableArray *detailStrings; inside a method, detailStrings is not automatically set to nil. So when you do
if ( detailStrings != nil ) { .. }
it enters the if statement and since it is not initialized, it will crash when you access detailStrings.
Another thing is that detailStrings won't be initialized if it enters the else part of the loop first. That will cause a crash too. So based on your requirement, either do
NSMutableArray *detailStrings = nil;
or initialize it before you enter the while loop.
Deepak said truth. You should initialize detailStrings with nil first.
But there is second possible issue:
I recommend also to set nil after release, because in the next loop you may test nonexistent part of memory with nil.
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
detailStrings = nil;
}
And the third possible issue: depending from incoming data you may go to the second part of IF statement first time and try to addObject into non-initialized array.
The fourth (hope last): you have memory leak with "checkers" arrays
Here's what I'm seeing:
//read in the file
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
//create the scanner
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
//declare some uninitialized stuff
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
//initialize some stuff
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
//begin loop
while(![fileScanner isAtEnd]){
//scan up to a newline
[fileScanner scanUpToString:#"\r" intoString:&held];
//see if you scanned a lowercase string
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
//make an array
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
//do a check... against an uninitialized value
if(detailStrings != nil){
//set a potentially uninitialized value into an array with an uninitialized key
[details setObject:detailStrings forKey:key];
At this point, you're pretty much hosed.
The fix:
properly initialize your variables
run the static analyzer
read the memory management programming guide
I developing an application, in which i working with database manipulation. The method i have written in database class as follows.
-(NSMutableArray *)getData: (NSString *)dbPath{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK){
NSString *sqlQuery = [NSString stringWithFormat:#"SELECT empID, addText FROM Employee WHERE nameID = %#", nameID];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, [sqlQuery UTF8String], -1, &selectstmt, NULL) == SQLITE_OK){
while (sqlite3_step(selectstmt) == SQLITE_ROW){
[dataArray addObject:[[NSMutableDictionary alloc] init]];
[[dataArray lastObject] setObject:[NSString
stringWithFormat:#"%d", sqlite3_column_int(selectstmt, 0)] forKey:#"empID"];
[[dataArray lastObject] setObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,1)] forKey:#"addText"];
}
}
sqlite3_finalize(selectstmt);
}
sqlite3_close(database);
return dataArray;
}
The above code work fine on the simulator but cannot on device.
I also was tracing the memory leaks , in which i founding that memory leak in above method code. But i not able to solve the that memory leak.
Now i also find out memory leak in following method.
(id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes
{
if ((self = [super init]))
{
_buffer = [str mutableCopy];
_attributes = [NSMutableArray arrayWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
}
return self;
}
The leak near _buffer = [str mutableCopy];. And leak trace gives me in the output continuous increasing NSCFString string allocation. How i maintain it?
Thanks in advance.
Your leak is that you don't release either the dataArray object, nor the mutable dictionaries you create in the while loop. Consider autoreleasing the mutable array, and manually releasing the dictionaries after you add them to the array.
As for why it "doesn't work" on the device, you need to be more specific about what happens and why that isn't what you expect.
Your inner loop leaks the NSMutableDictionary objects, as you should release them after adding to the array, i.e.
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:[NSString stringWithFormat:#"%d", sqlite3_column_int(selectstmt, 0)] forKey:#"empID"];
[dict setObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,1)] forKey:#"addText"];
[dataArray addObject:dict];
[dict release];
Also, your whole method should most probably return an autoreleased object by naming convention. Not sure if this is a leak - depends on how you call that method and if you release the returned value.
So maybe use
return [dataArray autorelease];
From the first glance you have 2 places where leaks can be:
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
...
return dataArray;
Caller method is responsible for releasing array returned from your method - check if it does.
Also your method name is not consistent with obj-c guidelines - they suggest that methods returning non-autoreleased object( so caller is responsible for releasing them) should contain create, alloc, copy in their name. So it could be better to return autoreleased array (return [dataArray autorelease]; from this method and let caller decide whether it needs to retain array or not.
Second place is
[dataArray addObject:[[NSMutableDictionary alloc] init]];
It is leaking dictionary object, you should probably just write
[dataArray addObject:[NSMutableDictionary dictionary]];
Your method contains two call to +alloc that don't have corresponding calls to -release or -autorelease.
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
...
[dataArray addObject:[[NSMutableDictionary alloc] init]];
You can rewrite these lines like this to get rid of the leak:
NSMutableArray *dataArray = [NSMutableArray array];
...
[dataArray addObject:[NSMutableDictionary dictionary]];
please go through the following code and please explain why is it crashes in last line?
NSMutableDictionary *dic1 = [[NSMutableDictionary alloc] initWithCapacity:10];
NSString *val = [[NSString alloc] initWithFormat:#"Deepak"];
NSString *key = [[NSString alloc] initWithFormat:#"First Name"];
int a = [val retainCount];
a = [key retainCount];
[dic1 setObject:val forKey:key];
a = [val retainCount];
a = [key retainCount];
//self.mainDic = [dic1 copy];
self.mainDic = [dic1 mutableCopy];//mainDic is like #property(copy) NSMutableDictionary *
[self.mainDic setObject:#"Hi" forKey:#"Good"];//Problem
Thanks.
copy properties are not suitable for mutable classes as they don't respect mutability and just send the copy message to the instances - what happens in the second last line is basically:
// ... release previous mainDic, if any
mainDic = [[dic1 mutableCopy] copy];
As the copy message results in an immutable version, NSDictionary, you are probably getting exception for an unrecognized selector -setObject:forKey: while debugging.
If you want to use copy properties you should provide your own setter instead and remove that manual mutableCopy - see e.g. Apples docs on the copy semantics.
I've almost never used a property with a modifier other than (nonatomic, retain)
this has saved me of a lot of problems,
that and on the dealloc method set'em to nil
this is the way I'd do it
NSMutableDictionary *dic1 = [[NSMutableDictionary alloc] initWithCapacity:10];
NSString *val = [[NSString alloc] initWithFormat:#"Deepak"];
NSString *key = [[NSString alloc] initWithFormat:#"First Name"];
[dic1 setObject:val forKey:key];
self.mainDic = dic1; //where mainDic is like #property (nonatomic, retain) NSMutableDictionary *
[self.mainDic setObject:#"Hi" forKey:#"Good"];
//let's clean this mess up
[val release];
[key release];
[dic1 release];
#Deepak ! your code itself works well. And there is no static analysis erro except un-referring a and memory leaking of dic1.
self.mainDic = [dic1 mutableCopy]; works well. Please run your code block again.
I created a new project and put your code and tested it. There was no problem.
#Georg Fritzsche your code is making crash.