Set instance variable twice with "self.variable = value" causes memory leak? - iphone

Until yesterday I thought that I had understood the iPhones memory management.
Well here is my problem:
// .h file
#property(nonatomic, retain) NSMutableDictionary *dicParams;
#property(nonatomic, retain) NSMutableDictionary *dicReferences;
#property(nonatomic, retain) FtMonitorHandler *monitorHandler;
// .m file
#synthesize dicParams, dicReferences, monitorHandler;
- (id)init {
self = [super init];
if (self) {
self.dicParams = [[NSMutableDictionary alloc] init];
self.dicReferences = [[NSMutableDictionary alloc] init];
self.monitorHandler = [[FtMonitorHandlerService alloc] init];
}
return self;
}
- (void)dealloc {
[monitorHandler release];
[dicParams release];
[dicReferences release];
[super dealloc];
}
If I set somewhere else, after the viewcontroller's allocation for example
self.dicParams = dicValues;
… it will turn into a leak
My understanding of setting instance variables with "self. …" was, that the current value will be "released" and then set with "retain".
I tried a little bit with instruments. Results:
-(void)createLeak {
self.dicParams = [[NSMutableDictionary alloc] init];
self.dicParams = [[NSMutableDictionary alloc] init];
}
-(void)createAnotherLeak {
self.dicParams = [[NSMutableDictionary alloc] init];
self.dicParams = nil;
self.dicParams = [[NSMutableDictionary alloc] init];
}
- (void)createWithoutLeak {
if(dicParams != nil) [dicParams release];
self.dicParams = [[NSMutableDictionary alloc] init];
}
Have I missed something, or is this the behavior as it should be?
EDIT: I tried to implement the suggested changes. It works fine, as long, as my variable is not GUI element. (UIView, UILabel, etc)
The autorelease will cause an app crash after a memory warning
- (void)loadView {
[super loadView];
// ... here is some other stuff ...
self.lblDeparture = [[[UILabel alloc] init] autorelease];
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
self.lblDeparture = nil;
}
- (void)dealloc {
[lblDeparture release];
[super dealloc];
}
I'm not quite sure, but I assume that the following lines are the real issue:
CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, INFO_VIEW_HEIGHT);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
[imageView addSubview:lblDeparture];
[lblDeparture release]; // is this correct?
[self.view addSubview:imageView];
[imageView release];

if you init you need to auto release.
-(void)dontCreateAnotherLeak {
self.dicParams = [[[NSMutableDictionary alloc] init] autorelease];
self.dicParams = nil;
self.dicParams = [[[NSMutableDictionary alloc] init] autorelease];
}
the easier equivalent is to use the convenience accessor.
self.dicParams = [NSMutableDictionary dictionary];
if you would like to handle this yourself. On top of the #synthesize dictParams; you will also want to create your own setter.
-(void)setDictParams:(NSMutableDictionary*) newDictParams
{
if (dictParams != newDictParams)
{
[dictParams release];
dictParams = [newDictParams retain];
}
}
this is a little simple. but essentially what the compiler creates with the retain modifier added to the #property tag

If you set a instance variable for which you have specified retain in property retain count becomes 1
Now as you call with reference to self as in case “self.variable = value” increase the retain count by 1, So the total retain count becomes 2.
So now to release it you need to bring retain count to 0. Hence you need to release it twice.
Hopew this helps.

I am not sure I understand the question fully, however your second part is easily explained...
-(void)createLeak {
self.dicParams = [[NSMutableDictionary alloc] init];
self.dicParams = [[NSMutableDictionary alloc] init];
that's clear...
now but this one
-(void)createAnotherLeak {
self.dicParams = [[NSMutableDictionary alloc] init];
self.dicParams = nil;
self.dicParams = [[NSMutableDictionary alloc] init]; }
does not release the first alloced self.dicParams but rather forgets any reference to it by setting it to nil and then resetting it with a new one. Setting to nil is not equal to release. If you would have created the first one with autorelease and then set it to nil it's something different. That should work correctly. And that's exatcly what you do with your 3rd example!
Now as to your inital question, what is it that leaks when you write
self.dicParams = dicValues;
?
the variable self.dicParams should just hold the value until you release it again

I recommend you read Apple's Memory Management Programming Guide carefully. http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
It's all explained in there.
There are a couple of obvious mistakes I can see that you are making.
Firstly, you shouldn't use accessors in init or dealloc.
So, this
- (id)init {
self = [super init];
if (self) {
self.dicParams = [[NSMutableDictionary alloc] init];
self.dicReferences = [[NSMutableDictionary alloc] init];
self.monitorHandler = [[FtMonitorHandlerService alloc] init];
}
return self;
}
should be
- (id)init {
self = [super init];
if (self) {
dicParams = [[NSMutableDictionary alloc] init];
dicReferences = [[NSMutableDictionary alloc] init];
monitorHandler = [[FtMonitorHandlerService alloc] init];
}
return self;
}
Secondly, when you set a retained property, the you need to release the whatever you are setting it to.
So, this
self.dicParams = [[NSMutableDictionary alloc] init];
should be
self.dicParams = [[[NSMutableDictionary alloc] init] autorelease];
or you can do this
NSMutableDictionary *newDicParams = [[NSMutableDictionary alloc] init];
self.dicParams = newDicParams;
[newDictParams release];

The setter generated by #synthesize for a (readwrite, retain, nonatomic) property looks something like this:
- (void) setSomething: (id) newSomething;
{
if (something != newSomething) {
[something release];
something = [newSomething retain];
}
}
The old object pointed to by the instance variable something will be released while the new object will be retained.
Your mistake is the object creation. You create your dictionary with [[NSDictionary alloc] init]. This dictionary has a retain count of 1. Your setter retains the object, so the new retain count is 2. When you call the setter again the retain count of your original dictionary correctly gets decreased - it’s 1 again. For the dictionary to be freed you’d have to release it again. For this there is autorelease. An autoreleased object will get released some time later. So the correct code to set your property would be
self.something = [[[NSDictionary alloc] init] autorelease];
or even better using the convenience allocator
self.something = [NSDictionary dictionary];
You really should read and understand Apple’s memory management guide - it’s all explained in there.
By the way, I talked about retain counts here. It’s OK to think about them, but you should never ask an object about it’s retain count, that value is useless, since it’s rarely what you would think.

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 or not release

I'm developing an iPhone application.
I have the following property:
#property (nonatomic, retain) Point2D* endPoint;
And this is a method on the same class:
- (id)initWithX:(CGFloat)x Y:(CGFloat)y;
{
if (self = [super init])
{
endPoint = [[Point2D alloc] initWithX:x Y:y];
...
}
And finally the dealloc method on the same class:
- (void)dealloc {
[endPoint release];
[super dealloc];
}
My question is it this code correct?
endPoint = [[Point2D alloc] initWithX:x Y:y];
Or maybe I have to do an autorelease here.
Go read the memory management guide as it'll explain all of this and a lot more.
In short, that code is correct.
If you did self.endPoint = [... alloc/init ...], then you'd need to autorelease or release in init to balance the extra retain.
Your assignment
endPoint = [[Point2D alloc] initWithX:x Y:y];
does not increase the retainCount, so if you want to keep endPoint to use later you don't use autorelease here.
Or you can use like this
self.endPoint = [[[Point2D alloc] initWithX:x Y:y] autorelease];
=> This assignment will increase the counter of endPoint.
You shouldn't use endPoint directly but rather through self.endPoint.
#property (nonatomic, retain) Point2D* endPoint;
- (id)initWithX:(CGFloat)x Y:(CGFloat)y;
{
if (self = [super init])
{
self.endPoint = [[[Point2D alloc] initWithX:x Y:y] autorelease]; //It'll retain it for us.
...
}
- (void)dealloc {
self.endPoint = nil; //setting it to nil means it'll release the object it was previously pointing to.
[super dealloc];
}
change endPoint = [[Point2D alloc] initWithX:x Y:y]; to
Point2D *temp = [[Point2D alloc] initWithX:x Y:y];
self.endPoint = temp;
[temp release];
to take advantage of the properties retaining setter.

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.

UIImage & UIImageView memory leak

i'm testing my app on Instruments,
and I see that my UIImage and UIImageView are memory-leaking like crazy...
I'm basically using recursion, so same variables get to load different images on each call.
nextImageName = [[NSString alloc] init];
nextImageName2 = [[NSString alloc] init];
nextImageName = [[currentPlayers objectAtIndex:playerIndex] retain];
nextImageName2 = [[currentPlayers objectAtIndex:(playerIndex+1)] retain];
nextImage = [[UIImage alloc] init];
nextImage2 = [[UIImage alloc] init];
nextImage = [UIImage imageNamed:nextImageName];
nextImage2 = [UIImage imageNamed:nextImageName2];
nextImageView = [[UIImageView alloc] init];
nextImageView2 = [[UIImageView alloc] init];
nextImageView = [[UIImageView alloc] initWithImage:nextImage];
nextImageView2 = [[UIImageView alloc] initWithImage:nextImage2];
NSLog(#"r:%d",currentRound);
NSLog(#"%d vs. %d", playerIndex, playerIndex+1);
buttonOne = [[UIButton alloc] init];
buttonTwo = [[UIButton alloc] init];
playerOne = nextImageView;
playerTwo = nextImageView2;
playerOne.frame = CGRectMake(180.0, 200.0, 275.0, 275.0);
playerTwo.frame = CGRectMake(550.0, 200, 275.0, 275.0);
buttonOne.frame = CGRectMake(180.0, 200.0, 275.0, 275.0);
buttonTwo.frame = CGRectMake(550.0, 200.0, 275.0, 275.0);
[buttonOne addTarget:self action:#selector(announceWinner:)
forControlEvents:UIControlEventTouchUpInside];
[buttonTwo addTarget:self action:#selector(announceWinner2:)
forControlEvents:UIControlEventTouchUpInside];
Could anyone please help me? This is driving me nuts..
I originally had release for all the variables at dealloc, but it seemed it didn't go into dealloc, so I also put it in viewDidUnload and didReceiveMemoryWarning.
I think the problem is this:
"I'm basically using recursion, so same variables get to load different images on each call"
...coupled with this:
"I originally had release for all the variables at dealloc, but it seemed it didn't go into dealloc, so I also put it in viewDidUnload and didReceiveMemoryWarning"
So essentially, if I understand your code correctly, you were making several passes through the alloc/init section over the lifetime of your class, but only ever calling release once, when the class itself is deallocated. I would expect that to leak like crazy.
You should be able to fix it by changing the alloc/init section to follow a pattern like:
if (nextImageName) {
//if it was previously set, release it so that the old instance doesn't leak
[nextImageName release];
}
nextImageName = [[NSString alloc] init];
if (nextImageName2) {
[nextImageName2 release];
}
nextImageName2 = [[NSString alloc] init];
//and so on...
This assumes that these variables are all declared as instance-variables on your class, and that in init you set them all to nil, like so:
- (void) init {
if ((self = [super init])) {
//set default values
nextImageName = nil;
nextImageName2 = nil;
//and so on...
//do other setup things here
}
}
nextImageName = [[NSString alloc] init];
nextImageName = [[currentPlayers objectAtIndex:playerIndex] retain];
there you have your first memleak.. you are allocating a string and then you replace that object with the one one from the array.
you can simply remove the first line.. you don't have to allocate a object before getting one out of an array.
But without seeing more code we can't see what you are releasing later.. I hope you are releasing stuff later ;)
You are allocating and init'ing each object and then increasing the retain count again.
Anytime you call alloc, copy, new or retain...the retain count of the object goes up by 1. When you release, the retain count goes down by 1. So when you type
nextImageName = [NSString alloc] init];
and then
nextImageName = [[currentPlayers objectAtIndex:playerIndex] retain];
nextImageName now has a retain count of 2. If you only used the second line, you would be fine. You don't need to allocate a new object, you just need to retain the one that's being returned to you.
Likewise nextImageView and nextImageView2 are being allocated twice and therefore have a retain count of 2. You only need the second call you're making.
nextImageView = [UIImageView alloc] initWithImage:nextImage];
Hope this helps.
Try this code. It will clear the memory leaks on UIImage and UIImageView.
nextImageName = [NSString string];
nextImageName2 = [NSString string];
nextImageName = [[currentPlayers objectAtIndex:playerIndex] retain];
nextImageName2 = [[currentPlayers objectAtIndex:(playerIndex+1)] retain];
nextImage = [UIImage imageNamed:nextImageName];
nextImage2 = [UIImage imageNamed:nextImageName2];
nextImageView = [[UIImageView alloc] initWithImage:nextImage];
nextImageView2 = [[UIImageView alloc] initWithImage:nextImage2];
NSLog(#"r:%d",currentRound);
NSLog(#"%d vs. %d", playerIndex, playerIndex+1);

IPhone Repository for data

I'm a totally noob in IPhone development. Just start a week ago. I'm trying to have this tableview working.
I have a class I created called CustomerRepository with a method like this
- (CustomerRepository *) init {
self = [super init];
if (self) {
customerArray = [[NSMutableArray alloc] init];
Customer *cust1 = [[Customer alloc] init];
cust1.name = #"cust1";
[customerArray addObject: cust1];
[cust1 release];
}
return self;
}
- (NSMutableArray *) GetAll {
NSMutableArray *returnCustomerArray = [[[NSMutableArray alloc] init] autorelease];
for(Customer *cust in customerArray)
{
Customer *copy = [[Customer alloc]init];
copy.name = cust.name;
[returnCustomerArray addObject:copy];
[copy release];
}
return returnCustomerArray;
}
Now In my Controller
#synthezise customerArray;
viewDidLoad {
CustomerRepository *custRepo = [[CustomerRepository alloc] init];
customerArray = [custRepo GetAll];
[custRepo release];
}
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section {
// It always throw an exception here about -[UITableViewRowData count]: unrecognized selector sent to instance
return [customerArray count];
}
There is definitely something wrong with my code, can you guys help me point out what is wrong. Most probably an access to an instance that is already release ....
You need to retain the array returned by GetAll. Try this:
viewDidLoad {
CustomerRepository *custRepo = [[CustomerRepository alloc] init];
customerArray = [[custRepo GetAll] retain];
[custRepo release];
}
If you don't retain it, then your autorelease in -GetAll means that the returned array will eventually be released. When your -numberOfRowsInSection method fires, it's talking to a dealloc'd instance.