release in uitableview doesn't work, but autorelease does - iphone

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.

Related

NSString crashing program, how to fix?

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.

"Potential Leak Error" - but I don't see it

I'm getting a "Potential leak" message upon doing an Analyze run of this code - which works perfectly well by the way, with no errors or crashes (its just a simple UINavigationController/TableView bit.)
The full message I get is: "Potential leak of an object allocated and stored into 'tempKey'"
It doesn't make sense to me - can anyone see it?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// create a tempKey String var, which will store the clicked-artist's name
// -- this here is the line the compiler says the error is in:
NSString *tempKey = [[NSString alloc] init];
if ([ArtisticStaffNames objectAtIndex:indexPath.row] == #"Jeff Smith")
tempKey = #"Jeff";
else if ([ArtisticStaffNames objectAtIndex:indexPath.row] == #"Dan Jones")
tempKey = #"Dan";
else if ([ArtisticStaffNames objectAtIndex:indexPath.row] == #"Matt Low")
tempKey = #"Mat";
else if ([ArtisticStaffNames objectAtIndex:indexPath.row] == #"Lisa Jennings")
tempKey = #"Lis";
else if ([ArtisticStaffNames objectAtIndex:indexPath.row] == #"Michael Bluarique")
tempKey = #"Mike";
artisticStaffDetailVC *artStaffVC = [[artisticStaffDetailVC alloc] initWithNibName: #"artisticStaffDetailVC" bundle:nil];
artStaffVC.key = tempKey;
[tempKey release];
// Sets the text of the BACK button on next screen to "back":
// alloc a UIBarButtonItem:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] init];
backButton.title = #"Staff";
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
// Pushes the next view/screen on:
[self.navigationController pushViewController:artStaffVC animated:YES];
[artStaffVC.key release];
}
The analyzer is correct. If you do this:
NSString* someString = [[NSString alloc] init];
You have a pointer to an NSString that you own. If you then do this:
someString = #"Blah";
You have assigned someString to point to a new NSString object, and leaked the first. That line does not simply change the existing string's contents. This is exactly what you're doing with your tempKey.
Use the tool called Instruments to see if you really are having a leak and where to find it.

Setting dictionary in singleton causing EXC_BAD_ACCESS

I'm having issues with a singleton I've created. It contains two NSMutableDictionary's, which are read and used in three views (and some modal views) throughout the app.
I've added an MKMapView t plot some of the venues inside the dictionaries on a map. When I use the exact same method/function used in every other view to access the data, I receive an EXC_BAD_ACCESS error pertaining to a deallocated dictionary. This comes from NSZombieEnabled:
CFDictionary retain: message sent to deallocated instance
In a dsym'ed trace, it is the replacement of one dictionary with another that is causing grief. The code I'm using to call the function comes from a MKAnnotationView click:
UIControl *tempButton = sender;
NSString *selectedEventsString = [self.eventsArray objectAtIndex:tempButton.tag];
NSLog(#"eventString: %#", selectedEventsString);
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:selectedEventsString];
[tempButton release];
[selectedEventsString release];
"selectedEventsString" is coming out to a perfectly corresponding event.
The corresponding code in EventsManager:
-(void)changeSelectedEventsDictionaryTo:(NSString *)eventName {
NSLog(#"singleton: %#", eventName);
self.eventString = eventName;
self.selectedEventsDictionary = [self.eventsDictionary objectForKey:eventName];
}
Both selectedEventsDictionary and eventsDictionary are set as #property (nonatomic, retain) in the .H file, and this is the init function:
+ (EventsManager*)eventsManager {
if (eventsManager == nil) {
eventsManager = [[super allocWithZone:NULL] init];
eventsManager.eventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.selectedEventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.eventString = [[NSString alloc] init];
eventsManager.mode = [[NSString alloc] init];
}
return eventsManager;
}
This is an example of code used in other views that works fine:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSString *eventString = [self.eventsArray objectAtIndex:row];
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:eventString];
//Modal display code here
}
Any help would be greatly appreciated! I think I've provided all relevant code but let me know if more is needed.
Cheers!
Where to start! I will point out some things that I do see wrong.
First Example. Do not release tempButton and selectedEventString as you never explicitly called retain/copy or alloc and init on them.
UIControl *tempButton = sender;
NSString *selectedEventsString = [self.eventsArray objectAtIndex:tempButton.tag];
NSLog(#"eventString: %#", selectedEventsString);
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:selectedEventsString];
//DO NOT RELEASE THESE YOU NEVER RETAINED THEM!
[tempButton release];
[selectedEventsString release];
Your static eventsManager is not thread safe which may not be a issue for you but should definitely be looked into.
Read the comments for the following code example
+ (EventsManager*)eventsManager {
if (eventsManager == nil) { //<-- Not thread safe
//DO NOT CALL SUPER USE self
//eventsManager = [[self alloc] init];
eventsManager = [[super allocWithZone:NULL] init];
//You need to autorelease these values or use an autoreleased static method
//eventsManager.eventsDictionary = [NSMutableDictionary dictionary];
//eventsManager.selectedEventsDictionary = [NSMutableDictionary dictionary];
eventsManager.eventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.selectedEventsDictionary = [[NSMutableDictionary alloc] init];
//Do not bother setting these at all or just set them to nil
eventsManager.eventString = [[NSString alloc] init];
eventsManager.mode = [[NSString alloc] init];
}
return eventsManager;
}
Make sure all of those properties are set to retain or copy and that may fix your problem. If you still have an issue after these fixes you can update your question and I will update my answer.

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?

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.