user707342
user707342

Reputation: 339

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

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];  

Upvotes: 2

Views: 815

Answers (5)

The Lazy Coder
The Lazy Coder

Reputation: 11828

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

Upvotes: 4

anon
anon

Reputation:

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.

Upvotes: 0

benwong
benwong

Reputation: 2226

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];

Upvotes: 0

user387184
user387184

Reputation: 11053

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

Upvotes: 0

Jhaliya - Praveen Sharma
Jhaliya - Praveen Sharma

Reputation: 31722

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.

Upvotes: 0

Related Questions