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

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.

Related

Wierd behaviour of a singleton object

I'm having really weird situation. I create a singletone object of a class named "Profile like this:
static Profile *currentProfile;
+ (Profile *)currentProfile
{
#synchronized(self)
{
if (currentProfile == nil)
currentProfile = [[Profile alloc] init];
}
return currentProfile;
}
- (id)init
{
self = [super init];
if (self)
{
// Initialization code here.
isChecked = [[[NSUserDefaults standardUserDefaults] objectForKey:#"isChecked"] boolValue];
if (isChecked)
{
NSLog(#"isChecked block is called");
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[self retain];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar replaceObjectAtIndex:i withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
}
else
{
password = #"";
pfefferID = #"";
email = #"";
avatar = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],nil];
isBoy = YES;
friends = [[NSMutableDictionary alloc] init];
rating = 0;
}
}
return self;
}
In init method i check a certain condition stored in NSUserDefaults by using BOOL variable named "isChecked". isChecked is equal to YES and everything works fine. But... i create this Profile object in AppDelegate file like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
users = [[NSMutableDictionary alloc] init];
usersForLeaderboardFromServer = [[NSMutableDictionary alloc] init];
listOfFriendsFromServer = [[NSMutableDictionary alloc] init];
currentProfile = [Profile currentProfile];
sessionID = 0;
if (!currentProfile.isChecked)//why????
NSLog(#"not checked");
if (currentProfile.isChecked)
{
[self getUsersForLeaderboardFromServer];
MainMenuView *mainMenu = [[[MainMenuView alloc] init] autorelease];
[self.navigationController pushViewController:mainMenu animated:YES];
}
}
So the same isChecked variable which a moment (far less than a moment actually) ago was equal to YES gets equal to NO when being used in application didFinishLaunchingWithOptions method by accessing it via dot. What's going on? I'm able to handle it but i'm just curious about this situation. Do you know what's wrong with it?
You're reassigning self in init, so you're returning the new object rather than the one you set isChecked on. See this code:
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[self retain];
It's slightly awkward to do things like you've got - I would certainly not recommend replacing it in the way you have. For a start, the value you set to the static currentProfile is not being updated when you reassign self so that's still the old one. And also you're not releasing the old self when you reassign.
To fix it you could do something like this:
id newSelf = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
newSelf.isChecked = isChecked;
[self release];
self = [newSelf retain];
But I don't really advocate that personally. I suggest you load in the object from your archive and then proceed to update yourself with it rather than reassigning self.

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.

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.

Strange behaviour when setting property in iPhone app

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
PushOnStackViewController *vc = [[PushOnStackViewController alloc] init];
vc.key = [self.keys objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:vc animated:YES];
}
and
in the init method of the PushOnStackViewController class I have
- (id)init {
self.navigationItem.title = key;
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"texts" ofType:#"plist"]];
self.keys = [dict objectForKey:key];
[dict release];
NSLog(#"%#", self.key);
NSLog(#"%i", [self.keys count]);
return self;
}
But why can't I access the self.key? It returns null, even though it has been set(it is a string).
When I access it in viewDidLoad it returns the correct value...anything I haven't read, or am I doing anything wrong?
Thanks in advance.
You can't access self.key inside the -init function because at that point it hasn't been set yet. you are setting it afterwards:
PushOnStackViewController *vc = [[PushOnStackViewController alloc] init]; // init runs here.
vc.key = [self.keys objectAtIndex:[indexPath row]]; // but you don't set the key property until here.
You might try adding a "key" parameter to the init function, like so:
-(id)initWithKey:(NSString*)key {
self = [super init];
if (self) {
self.key = key;
...etc...
}
return self;
}
Your init method is called before you set the property. Get rid of that init method and move your code into viewDidLoad to ensure that it's called after you've done all the property setup.
Don't create new init method for a UIViewController unless you know what you're doing. It's much easier to create a property (like you've done) and access that property inside the viewDidLoad method.

NSKeyedUnarchiver - Bad access

I was trying to archive an object into a plist file and load it later to fill a tableView.
It seems that the file gets archived correctly but I get a bad access when trying to get the value out of the file.
Am I doing something wrong?
This is where I save it
// Create some phonebook entries and store in array
NSMutableArray *book = [[NSMutableArray alloc] init];
Phonebook *chris = [[Phonebook alloc] init];
chris.name = #"Christian Sandrini";
chris.phone = #"1234567";
chris.mail = #"christian.sandrini#example.com";
[book addObject:chris];
[chris release];
Phonebook *sacha = [[Phonebook alloc] init];
sacha.name = #"Sacha Dubois";
sacha.phone = #"079 777 777";
sacha.mail = #"info#yzx.com";
[book addObject:sacha];
[sacha release];
Phonebook *steve = [[Phonebook alloc] init];
steve.name = #"Steve Solinger";
steve.phone = #"079 123 456";
steve.mail = #"steve.solinger#wuhu.com";
[book addObject:steve];
[steve release];
[NSKeyedArchiver archiveRootObject:book toFile:#"phonebook.plist"];
And here I try to get it out of the file and save it back into an array
- (void)viewDidLoad {
// Load Phone Book
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithFile:#"phonebook.plist"];
self.list = arr;
[arr release];
[super viewDidLoad];
}
The part where I try to build a cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *PhoneBookCellIdentifier = #"PhoneBookCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PhoneBookCellIdentifier];
if ( cell == nil )
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:PhoneBookCellIdentifier] autorelease];
}
NSUInteger row = [indexPath row];
Phonebook *book = [self.list objectAtIndex:row];
cell.textLabel.text = book.name;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
Here the bad access error
Current language: auto; currently
objective-c Assertion failed: (cls),
function getName, file
/SourceCache/objc4_Sim/objc4-427.5/runtime/objc-runtime-new.mm,
line 3990. Assertion failed: (cls),
function getName, file
/SourceCache/objc4_Sim/objc4-427.5/runtime/objc-runtime-new.mm,
line 3990. Assertion failed: (cls),
function getName, file
/SourceCache/objc4_Sim/objc4-427.5/runtime/objc-runtime-new.mm,
line 3990. Assertion failed: (cls),
function getName, file
/SourceCache/objc4_Sim/objc4-427.5/runtime/objc-runtime-new.mm,
line 3990.
Just to expand on that a fraction: unarchiveObjectWithFile will return an autoreleased pointer. You don't locally retain it so you shouldn't release. Because you do, the object is subsequently deallocated and by the time you come to use it by calling book.name, it's not there.
(I'm assuming the self.list property is retaining appropriately so that the object will be kept around as long as you don't release here. If not, you'll need to fix that too.)