I have a custom container UIViewController that has six child UIViewControllers, and a set of tabs that the user interacts with to switch between the child view controllers. The problem is when my container view controller is released the child view controllers are not.
I have verified that the child view controllers are not released by adding some debugging code to their dealloc methods, and they are released as long as their view's are not added to the container view controller's view.
Below is an a excerpt of the code I'm using to create my custom container view controller. The viewController pointers are iVars. I am also using ARC so that is why there are no actual release calls.
- (void)init
{
if ((self = [super init])) {
vc1 = [[UIViewController alloc] init];
[self addChildViewController:vc1];
vc2 = [[UIViewController alloc] init];
[self addChildViewController:vc2];
vc3 = [[UIViewController alloc] init];
[self addChildViewController:vc3];
vc4 = [[UIViewController alloc] init];
[self addChildViewController:vc4];
vc5 = [[UIViewController alloc] init];
[self addChildViewController:vc5];
vc6 = [[UIViewController alloc] init];
[self addChildViewController:vc6];
}
return self;
}
- (void)dealloc
{
[vc1 removeFromParentViewController];
vc1 = nil;
[vc2 removeFromParentViewController];
vc2 = nil;
[vc3 removeFromParentViewController];
vc3 = nil;
[vc4 removeFromParentViewController];
vc4 = nil;
[vc5 removeFromParentViewController];
vc5 = nil;
[vc6 removeFromParentViewController];
vc6 = nil;
}
- (void)switchFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
if (fromViewController) {
[fromViewController.view removeFromSuperview];
}
[self.view addSubview:toViewController];
toViewController.view.frame = self.view.bounds;
}
Do you all have any ideas what I'm doing wrong?
As I suspected, the problem is not related to the view controller containment code in the question, but rather your adding of the observers (which you discuss in your answer to this question):
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdateEventName object:nil queue:nil usingBlock:^(NSNotification *note) {
// do stuff when update happen
}];
And that you tried to remove it with
[[NSNotificationCenter defaultCenter] removeObserver:self name:kUpdateEventName object:nil];
So, there are two problems:
If you use addObserverForName:object:queue:
, this is not the correct way to remove this observer. Instead, define a property to keep track of the observer:
@property (nonatomic, weak) id<NSObject> notification;
Then save a reference to that observer when you create it:
self.notification = [[NSNotificationCenter defaultCenter] addObserverForName:kUpdateEventName object:nil queue:nil usingBlock:^(NSNotification *note) {
// do something
}];
And when you want to remove it, use this reference:
[[NSNotificationCenter defaultCenter] removeObserver:self.notification];
That would ensure that the observer would be removed properly.
The failure of the child view controllers to be released while this observer was in place means that this block that you passed to addObserverForName:object:queue:
must of had a reference to self
. If you tried to remove this observer correctly (as shown above) in dealloc
, you would still have a strong reference cycle (formerly known as a retain cycle). This is resolved in a number of ways, but the most robust pattern is to prevent the strong reference cycle in the first place by using the weakSelf
pattern:
typeof(self) __weak weakSelf = self;
self.notification = [[NSNotificationCenter defaultCenter] addObserverForName:kFooNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
// use `weakSelf` in this block; not `self`
}];
My original answer is below:
While Srikanth is right that after addChildViewController
, you should call didMoveToParentViewController:self
and before removeFromParentViewController
you should call willMoveToParentViewController:nil
. But that's not your problem. In fact, I used a variation of your code (even without the dealloc
), and the children controllers are released fine.
Bottom line, I suspect your problem rests elsewhere, probably a retain cycle somewhere. For example, do you children have strong reference to the parent? Are you using recurring timers? You reference some tabs. You're not using a tab bar controller, are you? It's got to be something like that.
[Refer to revision history if you want to see rest of the original answer with code snippets and minor details on the OP's code sample]
This is not the way to add and remove child view controllers
[childViewController willMoveToParentViewController:nil];
[childViewController view] removeFromSuperview];
[childViewController removeFromParentViewController];
is the way to remove and to add it is
[parentViewController addChildViewController:childViewController];
[parentViewController.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:parentViewController];
After hours and hours of trying to figure out what was going on I finally found what was causing my child view controllers from releasing correctly.
Each view controller had the following notification declared in each so that they could respond to various events.
[[NSNotificationCenter defaultCenter] addObserverForName:UPDATE_EVENT object:nil queue:nil usingBlock:^(NSNotification *note) {
// do stuff when update happen
}];
Turns out for some reason that was keeping my view controllers from releasing correctly. I'm guessing that this added the view controller to the NSNotificationCenters observer list, and wasn't being removed when I did the following line.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UPDATE_EVENT object:nil];
So to fix my problem I just changed the notification to register like below.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateEvent:) name:UPDATE_EVENT object:nil];
I have no idea why the way I was registering the notification wasn't allowing my view controller to release correctly, but this seemed to have fixed it. If anyone has any insight on why this issue would happen then please let me know.
Thanks!