Objective-C memory management: What is happening when setting a retain property? - objective-c

I'd like to understand the memory management implications of the following line of code:
// in tableView:cellForRowAtIndexPath
cell.accessoryView = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"test.png"];
I'm calling alloc which usually means I call release somewhere. The UITableViewCell's accessoryView setter property is retain so (I think) the cell will "take ownership" of the of the UIImageView. What exactly is happening in the above line of code in regards to memory management?

If you don't release the view somewhere then it will be leaked. So you might want to do
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"test.png"];
cell.accessoryView = imageView;
[imageView release];
or you could also do
cell.accessoryView = [[[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"test.png"] autorelease];

First: +alloc retains the UIImageView (or, alternately, "you start with ownership of the UIImageView")
Second: +imageNamed autoreleases the UIImage (or, "+imageNamed does not give you ownership of the UIImage")
Third: the setter for accessoryView retains the UIImageView (or, "the accessory view takes ownership of the UIImageView")
Since you now have two owners for the UIImageView, that's probably a leak unless you're intentionally keeping it around to use later, and managing it accordingly.

Think of it this way: you're calling alloc/init, so you own it. You must release it when you no longer want to own it.
You can assume that cell.accessoryView takes ownership unless the docs say otherwise (like with delegates), so once you assign it to cell.accessoryView, you probably don't need to own it anymore. You should release it.
In summary, that line is retaining at least twice: once with the alloc/init and at least once with the assignment to cell.accessoryView. You are only responsible for one release, the one for alloc/init.

Related

How to release an allocated bar button item

Can I autorelease the following?
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:backButton] autorelease];
it works and removes an Analyzer warning I was getting. if not, how would I go about releasing it correctly.
thanks for any help
This is correct.
The leftBarButtonItem is a property that retains the UIBarButtonItem, so yes it is a good approach.
Without the autorelease message it will cause a memory leak.
it is correct, also you can do this
UIBarButtonItem *item = [[[UIBarButtonItem alloc] initWithCustomView:backButton];
self.navigationItem.leftBarButtonItem = item;
[item release];
That's the right way (although the right way now is to use ARC).
An accepted alternative (in non-ARC environment) is:
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithCustomView:backButton];
self.navigationItem.leftBarButtonItem = barButton;
[barButton release];
The setter of navigationItem retains the button itself so the object returned by alloc/init has to be released in order to balance the retain counts in one of the
described ways (alloc/init returns object with retaincount 1 and the setter adds +1 to that).
This is definitely NOT recommended:
[self.navigationItem.leftBarButtonItem release];

Do I need to remove views from superviews in dealloc?

If I alloc/init a view and add it to a another view in code (I did not use a xib) - do I need to remove it when the containing UIViewController's dealloc message is sent? I have seen this code in certain places, and wondered is it necessary under some circumstances to free memory?
Thanks,
Marc
If you do this,
UIView *v = [[UIView alloc] init];
[self.view addSubview:v];
[v release];
or
UIView *v = [[[UIView alloc] init] autorelease];
[self.view addSubview:v];
,the v will be released when its parent view release;
When the parent view use addSubview, it will retain the subview, and will release the subview when it is released.
This isn't necessary. All UIView subclasses hold subviews array, which gets released in the final UIView dealloc message, which releases your views.

Memory Management

How is method removeFromSuperView: really works?
I got a problem of memory bad access when I want to reinit the view
- (id)init {
if (!(self = [super init]))
return nil;
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
NSLog(#"retainCount :%d", [_mainView retainCount]);
UIButton *reInitButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f,0.0f,90.0f,35.0f)];
[reInitButton addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
return self;
}
- (void)buttonDidTapped:(id)sender {
[_mainView removeFromSuperView]; //crash during second times press the button
NSLog(#"retainCount :%d", [_mainView retainCount]);
_mainView = [[UIView alloc] initWithFrame[[UIScreen mainScreen] bounds]];
[[self view] addSubView:_mainView];
NSLog(#"retainCount :%d", [_mainView retainCount]);
[_mainView release];
NSLog(#"retainCount :%d", [_mainView retainCount]);
}
I have NSLog every times there are any retain or alloc or release keyword. And the result is very weird.
//init
retainCount : 1
retainCount : 2
retainCount : 1
//1st time pressed button
retainCount : 1 //remove super view didn't decrease
retainCount : 2
retainCount : 1
//2nd time pressed button
retainCount : 0 //crash. Memory bad access
The weird thing is why it didn't crash on 1st time pressed??
I think your problem is here:
[_mainView release];
You've dropped your reference to _mainView, and yet, by my reading, that's a member variable that you'll keep around and continue to invoke methods on. That's not valid. Once you've called -release, you've essentially told the system you're not going to use that object again, and you can't do anything useful with a stale pointer to that object, like you do when you call -removeFromSuperView on it later.
If you want to continue to keep _mainView around and call code on it, you need to keep a reference. Perhaps you should move the release to your object's -dealloc method. Alternatively you could -release it in the button method and re-create a new view the next time you need to.
As a helpful tip, a lot of programmers like to reset objects to NULL (or nil in objC-speak) after releasing them, as a reminder that you can't use that object again. If you -release something, you'd better mean it.
Lastly, I suggest you Google the term "reference counting" and read up on it; it's a more generic idiom than the specifics of NSObject, and it is likely to be useful to think of the basics and how you might implement this in another language, like say, C. This will help you reason better about reference counted objects.
NEVER USE RETAINCOUNT. Sorry for putting that in caps, but I can't figure out for the life of me why people still use it. It's a faulty reference for memory management. Use instruments or similar instead.
You shouldn't be accessing _mainView at that point. This may be hard to explain, so bear with me. We're going to count, but not absolute retain count, just your code's claims on the object.
You allocate memory for an object and point at it with _mainView:
_mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
You have 1 claim of ownership to that object. When you add it as the subview of another view, that view likewise makes a claim of ownership, but that's not yours, it's the view's. The fact that it makes the object in _mainView stick around is an accident, and you shouldn't rely on it. Then you release the object:
[_mainView release];
You have relinquished your ownership claim -- you now have 0 claims, and you should no longer try to access this object. You don't own it. Again, the fact that it still exists because another view is using it, and the fact that you still have a pointer to it, are accidents*, and you should not rely on them.
When it comes time to handle your button press, then, you are accessing an object over which you have no ownership:
[_mainView removeFromSuperView];
and this causes a crash, which may not be expected, but it is not unreasonable. By letting your claims of ownership go to 0, you told the system "I don't need this object anymore. I'm not going to access it after this point. If it disappears, I will not be affected." In fact, though, you do need it to stay around, and you do need to access it.
What you should do, then, is move the line:
[_mainView release];
to inside the button action, right after the call to removeFromSuperview.
*The second of which could be avoided by setting _mainView = nil; after you release it, in this case, but that won't solve the greater problem.

Subview of UIButton needs releasing?

First time asking a question here.
I have a question, I have a UIImageView added as a subview to my UIButton, which is declared using buttonWithType: (which means I don't have to release the button right?) But do I still have to release the subview of my UIButton?
Code bits:
UIImage *circleImage = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"item-circle" ofType: #"png"]];
UIImageView *circleImageView = [[[UIImageView alloc] initWithImage: circleImage] autorelease];
[imageView setFrame: CGRectMake(-5, -5, 65, 65)];
UIButton *button = [UIButton buttonWithType: UIButtonTypeCustom];
[button addSubview: circleImageView];
Short Answer:
Your code seems fine. The basic rule of thumb is that for every alloc, new, retain, or copy, you need a release or autorelease.
Long Answer:
Let's go through your code line by line.
UIImage *circleImage = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"item-circle" ofType: #"png"]];
This first line uses a convenience method. you don't need to call release on anything, since you haven't called alloc, new, retain, or copy.
UIImageView *circleImageView = [[[UIImageView alloc] initWithImage: circleImage] autorelease];
In the second line, you call alloc, but you then call autoerelease, so you're good there.
[imageView setFrame: CGRectMake(-5, -5, 65, 65)];
Again, no alloc, new, retain, or copy.
UIButton *button = [UIButton buttonWithType: UIButtonTypeCustom];
Once again, you've used a convenience method.
[button addSubview: circleImageView];
You still haven't called alloc, new, retain, or copy. Therefore, you don't call release or autorelease.
As a general rule, anything you alloc or retain yourself will need to be released. But you are doing that here (in essence) by calling autorelease. If you're asking whether or not you need to release the subview again, the answer is no.
The same applies to your button. You haven't called alloc or retain (instead you used buttonWithType), so you don't need to call release on it.

UINavigationController crashes due to a NSArray variable

I push a view controller into current navigation controller. It works fine, except when I am getting out of the current view controller, it crashes.
MyTableView *newPage = [[MyTableView alloc] initWithNibName:#"table2" bundle:nil];
[[self navigationController] pushViewController:newPage animated:YES];
//[newPage release];
I comment out the last line to prevent crash. I read another post about variables being over released. In the newPage, I only have one variable (arrCellText), and is initialized in the initWithNibName
NSArray *temp = [[NSArray alloc] initWithObjects:#"string1", #"string2", #"string3", nil];
[self setArrCellText: temp];
[temp release];
I put the release in the dealloc
[arrCellText release];
If I comment out setting and release of arrCellText, it works fine too.
I must not have complete understanding of memory management, and I would like to understand this better. TIA
Where does the crash happen exactly?
First you can release 'newPage' after pushing it onto the navigationController (because it's retained there).
You might try to access anything from newPage after coming back. 'newPage' was released in the mean time and thus has some garbage value (but not nil).