Sending release to an NSArray of UIViewControllers? - iphone

If I have an NSArray of UIViewControllers and send release to the array, will that call viewDidUnload or dealloc for each of the UIViewControllers? or neither?
Here's what I'm doing:
- (void) viewDidLoad {
UIViewController* profileController = [[ProfileController alloc] init];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc] initWithObjects: profileController, dietController, exerciseController, progressController, friendsController, nil];
[profileController release];
//other controllers get released same way ....
}
- (void) dealloc {
[viewControllers release];
NSLog("DEALLOC!");
//I know dealloc is being called
//what happens to the view controllers?
}
I put a breakpoint in the viewDidUnload and dealloc methods for each of these view controllers, and they don't get called.

As pgb mentioned, you are confusing the two concepts of memory management and view controller life cycle. viewDidUnload will be called whenever the view controllers view is unloaded, which of course will only happen if the view is loaded. In the absence of displaying any of the view controllers you should not expect to see viewDidUnload called at all.
You very much should expect to see dealloc called though!
Your problem is with the initialisation of the viewControllers array:
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
Assuming that viewControllers is a property that is using the retain attribute, this will leak the array, and thus all the array's contents. The problem is that you have alloc'd a mutable array (thus retain count = 1), then you are assigning it to the viewControllers property which will increment the retain count.
In your dealloc method you (correctly) release the array, but this will merely decrement the retain count to one.
My suggested fix is to add autorelease to the above code:
self.viewControllers = [[[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil] autorelease];
After making this change you should expect to see dealloc being called on the view controllers. You should also expect to see viewDidUnload called as the views of these view controllers are unloaded (for example, as they are popped off of the stack in a navigation-controller based application).

I see two issues here, one for each of the methods not called:
viewDidUnload won't be called if the view associated with the UIViewController wasn't loaded. You are basically mixing two concepts: memory management (and object lifecycle) vs. view controller life cycle. While you can see some parallelism between the two, they are not necessarily related.
Are you, at any point, pushing the viewController so its view is visible? If you are not, then viewDidUnload will certainly not be called (as won't viewDidLoad).
As for dealloc, if it's not called after you release the object (and you think the object should be dealloced from memory) it's because you have a memory leak. In your code, I can easily see the memory leak on the initialization code:
UIViewController* profileController = [[ProfileController alloc] init];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
profileController gets its retainCount bumped on the first line, when you alloc it. Later, when you add it to a NSMutableArray, the array will retain it, bumping its retainCount once again. To balance that, you would need to release twice, but you are only releaseing once, on your dealloc method. To solve this issue, I would change your initialization to:
UIViewController* profileController = [[[ProfileController alloc] init] autorelease];
..........
//all the other controllers get allocated same way
self.viewControllers = [[NSMutableArray alloc]
initWithObjects: profileController,
dietController,
exerciseController,
progressController,
friendsController,
nil];
which will add the extra release you are missing and balance the retain release calls on your view controllers.

When you release the array, that does not dealloc it, that gives it permission to be dealloced if there are no other retains.
Similarly, if the array is dealloced, each element inside is released, but the objects may be retained elsewhere and hence are not guaranteed to be dealloced immediately.
In general, an object like a view or view controller is retained by the UI logic while it's actively being presented, so releasing your retains on it, either directly or through the array, will not cause it to be dealloced if it's being displayed (or, eg, in the stack of a navigation controller).

Related

viewController object BAD_ACCESS in iOS5?

Usually when we push viewcontroller, we will create object for view controller, after that line we will push and release it.
But it gets crashed when we run in iOS5. So I retained the object through propery and declare it in interface as global. Now it is working fine. Will retaining viewcontroller occupy much memory? What is the difference between following two approches?
One:
MyViewCOntroller *obj = [[MyViewCOntroller alloc] init];
[self.navigationController pushViewController:obj Animated:YES];
[obj Release]
Two:
self.obj = [[MyViewCOntroller alloc] init];
[self.navigationController pushViewController:self.obj Animated:YES];
[self.obj Release]
The first one should be right and please detect for the crash reason again. It can't be crashed when you use the first one to push a new view controller.
As for the difference: in the second one, if you declare the obj as a var of self class and you don't use ARC, you take care of obj like the other instance vars. You just need to do release in the dealloc.
Generally speaking, you should not release property(self.obj) in methods except for dealloc.
The second code snippet should be replaced like this:
self.obj = [[MyViewCOntroller alloc] init];
[self.navigationController pushViewController:self.obj Animated:YES];
And add below one to your dealloc method:
self.obj = nil; // Property will release itself and set the point to nil
The first code snippet is OK, you alloced local instance and released it after used.
why aren't you init your view controller with nib?
SearchView *secondViewController = [[SearchView alloc] initWithNibName:#"SearchView" bundle:nil];
[self.navigationController pushViewController:secondViewController animated:YES];
[secondViewController release];
be careful with retaining any objects. You must be completely sure that you init it only onse and then release it. If you do so, you may not care about memory. The difference between your inits is: in 1st case you creating ner object. It is NOT retain, but, may be leak, I'm not shure. I think you should add autorelease. In 2nd case you have an object's property (probebly, retain?) in header. you must release it in dealloc method
Are you passing the View Controller to the new Object?
If yes, Are you releasing this View Controller property in the new View Controller's dealloc method? That would be a double release.
Example no. 2 would solve this problem because of the (retain) type property it may have in the old View Controller, would set its retain count to 2.

Where is the leak?

The Instruments tells me that this piece of code has a leak. However, I am pretty sure I have released it later on. Can anyone tell me what's going on here?
- (void) addReminderEntry{
DataEntryController* item = [[DataEntryController alloc] initWithEntryType:REMINDER]; // it says that the leak was instantiated here
item.delegate = self;
[[self navigationController] pushViewController:item animated:YES];
[item setEditing:YES animated:YES];
[item release];// this is the place I release it
}
Thanks
More than likely it has to do with something not being released within the DataEntryController class. Make sure you are releasing all your properties/etc within that class.
Leaks tells you only where memory was allocated, what it cannot tell you is where to put the code that should have released it to start with!
So this is saying that you made a view controller, and it was still around in memory after you finished with it. Yes you release the VC in that code, but only after you present it - which means the navigation controller has retained it, and possibly other things. It only gets deallocated when the final release is called.
The main culprit for view controllers not being released is usually having the view controller set itself as a delegate for something it retains, and then not undoing that when the view controller goes offscreen. If your view controller is a delegate of something that retains it, it's never going to be deallocated.
It turns out that this is caused by this constructor:
- (DataEntryController*) initWithEntryType:(DataType) eType{
DataEntryController* item = [[DataEntryController alloc] init];//<- here
item.entryType = eType;
item.allowEdit = YES;
return item;
}
Apparently iOS adds retain 1 to each constructor with an initial 'init'.
It works fine after switching to:
DataEntryController* item = [super init];

when to alloc and initialize a view controller

recently I joined two Xcode projects together. To get this thing to work, I had to alloc and initialize my view controller.
self.myViewController = [[MyViewController alloc] init];
But why? In the other project I have the same code. The sole difference is hierarchy of the different views. I added a new view to the top (beginning). So the calling View Controller is not the first view on the stack anymore.
I'm pushing my view in this way on the stack:
[[self navigationController] pushViewController:myViewController animated:YES];
In my NIBs I have added a View Controller object with IB and connected the Outlets.
And I have a memory management question too: If I have a property like myViewController, do I have to release it? The "normal" release is done in the dealloc method. But do I have to use an additional release because of the alloc? I don't think so, but I ask you anyway.
I would need to see more code to answer why you had to alloc your view controller, but I'd say that you always alloc them manually (at least in my experience).
As for the memory management question, if your property is declared as a retain property (#property(retain) UIViewController *myViewController), you are indeed leaking memory, since the retain count after alloc will be 1, and after the retain done by your accessor will be 2. Hence, if you release it only once, you'll end up with a leak.
I usually do this instead:
self.myViewController = [[[MyViewController alloc] init] autorelease];
I found it out: In IB I had to set the nib name on my view controller object. So the alloc and initialization is done by IB?
There is one more option:
(IBAction)loginButton:(UIButton *)sender {
NSLog(#"pressed login");
ICMasterViewController *controller = [[self storyboard] instantiateViewControllerWithIdentifier:#"mainnav"];
[self presentViewController:controller animated:YES completion:nil];
}
On your storyboard you must have UIViewController with name mainnav

differing methods of alloc / init / retaining an object in objective-c

In several pieces of sample objective-c code I've seen people create new objects like this:
RootViewController *viewController = [[RootViewController alloc] init];
self.rootViewController = viewController; // self.rootViewController is a (nonatomic,retain) synthesized property
[viewController release];
[window addSubview: [self.rootViewController view]];
Is that any different "behind the scenes" than doing it like this instead?
self.rootViewController = [[RootViewController alloc] init];
[window addSubview: [self.rootViewController view]];
Edit: later I release rootViewController in my dealloc method:
-(void) dealloc {
[rootViewController release];
[super dealloc];
}
I'm just curious about the syntax between the two. One seems to create a temporary viewController object, and the other allocs/inits directly to self.rootViewController.
Seems a bit more straightforward/streamlined that way so I'm wondering why anyone would opt for the first method.
Thanks!
Answer:
So it looks like when using the second method it causes a memory leak because the rootViewController object would actually have a retain count of 2. (See my answer below with a link to a post that thoroughly explains it.) Thanks everyone!
This line:
self.rootViewController = viewController
is identical to this line:
[self setRootViewController:viewController];
The typical setX call will release a previously retained value of X and assign a new retained value of X.
id old = X;
X = [new retain];
[old release];
But it can do anything else as well.
If you know there is not a current value to release (in init) and the setter function does nothing but retain the new value (synthesized), you can replace:
RootViewController *viewController = [[RootViewController alloc] init];
self.rootViewController = viewController; // self.rootViewController is a (nonatomic,retain) synthesized property
[viewController release];
With:
rootViewController = [[RootViewController alloc] init];
which does not use self. and so directly assigns a value instead of calling a setter method. Using the setter method is generally preferred.
To consolidate the lines as you want, you can also switch to this:
self.rootViewController = [[[RootViewController alloc] init] autorelease];
Which uses the setter method, releases the allocated instance, and fits on one line.
Your second code snipppet doesn't release the object you created.
self.rootViewController is a property that retains the object. So you're creating an object using alloc, and then the setter method for self.rootViewController will retain it also. You should release all objects that you allocated. Always
What happens is:
You create an object of type RootViewController using alloc, so the retain count becomes 1
The object is assigned to a property which also retains the object. So the retain count becomes 2
When self is deallocated later on, the retained RootViewController object will be released, so its retain count becomes 1 again.
Result: you have a memory leak.
In the second code snippet there will be a memory leak because you omit the [self.rootViewController release] line.
In more detail:
When you call [[RootViewController alloc] init], the retain count of the created object will be 1.
Calling self.rootViewController = viewController will increase it to 2 because the self.rootViewController property is retaining.
Calling [viewController release] decreases the retain count to 1
So if you call self.rootViewController = nil later, then the retain count will be 0 (because the generated setter calls a release method), so the object will be deallocated.
In the second case, the retain count will be 1 when you call self.rootViewController = nil, so the object will never be released.
If you want a more compact solution, try this:
self.rootViewController = [[[RootViewController alloc] init] autorelease];
[window addSubview: [self.rootViewController view]];
Please read and understand the Cocoa Memory MAnagement Rules. You obtained the object with alloc, therefore you have ownership of it. By "you" I mean the executing code in the current scope. You need to either release or autorelease it to relinquish ownership. Assigning the object to something else (in this case a property) does not absolve you of your responsibility to relinquish ownership when done.
Your second example leaks. You can fix it thusly:
self.rootViewController = [[[RootViewController alloc] init] autorelease];
In Mac OS X, whether you use that or your first example is a just a matter of preferred style. With the iPhone, the first example is generally preferred because it doesn't involve adding an object to the autorelease pool. Having said that, since a view controller will probably need to stick around beyond the end of the current event, it makes little difference.
By the way, two answers have mentioned retain counts and they are both correct, but it is better to avoid thinking about retain counts at all and think only in terms of ownership. Retain counts are an implementation detail.
Thank you guys for your answers! I was looking for less "read the memory management guide" type of answers and more of a "dumbed down," "here's the background and difference between these two methods laid out for you" type of answer. I'm familiar with object ownership, retain counts, etc. but I didn't realize WHY using **self.**rootViewController was important, and WHY the second code snippet was leaking... and literally the "behind the scenes" difference between the two. So I came across this post which was I feel was the exact answer I was looking for... (I hope it's accurate!) :) but anyway I'm giving the ole check mark to Philippe because he answered first. I just didn't understand his answer until reading the following post...
http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/7295-getters-setters-properties-newbie.html
This part was key for me:
So what would happen if you wrote:
self.obj = [[SomeObject alloc] init];
In this case, you're holding onto an object with a retain count of two - the first count comes from the "alloc" and the second one is added by the setter.
To release this variable, you'd have to do something like this:
[obj release];
self.obj = newValue;
so that "release" gets called twice on the object. If you omit the extra "release", then when the pointer gets overwritten the object will still be floating around with a retain count of one, and thus doesn't get deallocated. Instant memory leak.
Thanks again!

UINavigationController and memory management

- (void)launchSearch
{
EventsSearchViewController *searchController = [[EventsSearchViewController alloc] initWithNibName:#"EventsSearchView" bundle:nil];
[self.navigationController pushViewController:searchController animated:YES];
//[searchController release];
}
Notice the [searchController release] is commented out. I've understood that pushing searchController onto the navigation controller retains it, and I should release it from my code. I did just alloc/init it, after all, and if I don't free it, it'll leak.
With that line commented out, navigation works great. With it NOT commented out, I can navigate INTO this view okay, but coming back UP a level crashes with a *** -[CFArray release]: message sent to deallocated instance 0x443a9e0 error.
What's happening here? Is the NavigationController releasing it for me somehow when it goes out of view?
The boilerplate that comes on a UINavigationController template in XCode has the newly-pushed controller getting released. But when I do it, it fails.
---EDIT----
So this morning, I sit down, and it works. No real clue why. Sigh.
Taking what I thought I learned, then, and applying it to another piece of this same controller, I did the following. Yesterday I had this code WITHOUT the release statements, because it didn't work right with them. So this morning I added them to create:
- (IBAction)switchView:(id)sender
{
UISegmentedControl *seg = (UISegmentedControl *)sender;
NSInteger choice = [seg selectedSegmentIndex];
NSArray *array = [mainView subviews];
UIView *oldView = [array objectAtIndex:0];
[oldView removeFromSuperview];
if (choice == 0) {
tableController = [[EventsTableViewController alloc]
initWithNibName:#"EventsTableView" bundle:nil];
[mainView addSubview:tableController.view];
[tableController release];
}
if (choice == 1) {
calendarController = [[EventsCalendarViewController alloc]
initWithNibName:#"EventsCalendarView" bundle:nil];
[mainView addSubview:calendarController.view];
[calendarController release];
}
if (choice == 2) {
mapController = [[EventsMapViewController alloc]
initWithNibName:#"EventsMapView" bundle:nil];
[mainView addSubview:mapController.view];
[mapController release];
}
}
With it set up like this, when I come onto the view, the main portal of my view is filled with the EventsTableViewController's view, I can click to mapView and calendarView, but when I go BACK to tableView, I die because the table delegate methods are being called on a deallocated instance.
So I went and made all of these controllers into synthesized properties, so I can release them in [dealloc]. Which seems to work, but the real question is why adding these views as subviews doesn't retain them, passing ownership to the new view it's a member of, allowing me to release them right there?
Wow, guys. Thanks so much for all your responses--tragically I sent you all on a horrible goosechase.
My NavigationView navigates a NSArray of Event objects (local arts events). My table view drills down to a detail view.
My detail view has in it the following:
-(void)loadEvent:(Event *)event
{
thisEvent = event;
}
And I call that from my table view before pushing the detail view onto the nav stack. thisEvent is a synthesized property of type Event, and so since it's synthesized, I dutifully release'd it in [dealloc].
Many of you already see the problem. Backing up to the table view, when I scroll such that the one I just saw is displayed, it builds the custom table row, and so it goes to get the title property from the Event.... which I just released inside the detail controller. Boom.
I added a retain to that loadEvent: method above and the crashes, they are gone.
NONE of this was really about the views getting retained and released by the navcontroller. It was about accidentally over-releasing the data objects I'm navigating. Part of what had me discover this was, I NSLogged myself in the [dealloc] of each of these view controllers, and I can now see they're behaving exactly as they should.
Thanks! I SO love this site.
I'd guess the fault lies in EventsSearchViewController's init. Is it returning an autoreleased self by mistake ?
Looks like EventsSearchViewController is allocating an array and then over-releasing it, with one of the releases probably in its dealloc.
If you comment out the release, your EventsSearchViewController is never deallocated (it leaks). So, errors that occur as a result of its own dealloc will be masked since that method won't be called. Releasing the controller is the right thing, but you have another bug in the controller itself that only appears at dealloc time.
It could also be that dealloc is releasing an autoreleased array, so you may not have two explicit release calls in your code. But it looks very much like releasing something in dealloc that's causing the problem.