I have a login screen that I allocate in one place, and dismiss in another, and upon dismissal, its dealloc method is never called and the iVar holding the login screen still has a value even after being assigned nil in my dismissal code.
Here is my allocation
-(void)loginUser
{
loginScreen = [[LoginScreen alloc] initWithNibName:#"LoginScreen" bundle:nil];
[self.tabBarController addChildViewController:loginScreen];
[self.tabBarController.view addSubview:loginScreen.view];
[loginScreen didMoveToParentViewController:self.tabBarController];
[self.tabBarController.view bringSubviewToFront:loginScreen.view];
}
Here is my dismissal and deallocation in another method (which fails to deallocate)
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
loginScreen.view.frame = CGRectOffset(frame, -1024, 0);
}
completion:^(BOOL finished) {
// Remove the loginScreen
[loginScreen willMoveToParentViewController:nil];
[loginScreen.view removeFromSuperview];
[loginScreen removeFromParentViewController];
[loginScreen cleanupBeforeDealloc];
loginScreen = nil;
}];
I have some code that listens for keyboard notifications inside LoginScreen, but I added a method below to clean that up, and I tried calling it in my dismissal code above, but that still didn't fix it. grrrr.
-(void)cleanupBeforeDealloc
{
[self deregisterFromKeyboardNotifications];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
I would suggest getting rid of loginScreen entirely. There is no need for it, as this is a child view controller and is accessible through your childViewControllers array. childViewControllers manages the retain and release for the child view controller, and your loginScreen property is adding an extra retain that could be messing things up.
However, the actual cause of the retain cycle is probably that the view controller has registered and retained an observer with the notification center. That is a common cause of retain cycles. The notification center retains the observer and the observer retains self. You cannot unregister in dealloc to break the cycle because the retain means that dealloc isn't called.
Related
I have a view controller(VC1) embedded in a navigation controller(NAV1). In its viewWillAppear method, I make a call to modally present another view controller. In one case I need the new view controller(VC2) to be presented with animation, and in another case it should be presented without animation. VC2 is also embedded in its own navigation controller(NAV2).
All is fine when the animation flag is set to TRUE. When I set the flag to FALSE, couple of things go wrong:
1. I get the following warning in the console: Presenting view controllers on detached view controllers is discouraged
2. When I move back from VC2 after calling dismissViewControllerAnimated:FALSE completion:nil the viewWillAppear method of VC1 does not get called. It gets called if the animation flag is set to TRUE.
In VC1:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff:)
withObject:nil];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
[Utility presentViewController:pNavController
fromViewController:self
animated:FALSE
completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
IN VC2:
[Utility dismissViewController:self
animated:FALSE
completion:nil];
The above method calls the dismissViewControllerAnimated: method.
Not a solution but a workaround:
You can easily postpone any UI operation to the very next event loop by using afterDelay:0:
[self performSelector:#selector(importStuff:)
withObject:nil
afterDelay:0];
This will give a chance to the current operation to complete.
Furthermore, a delay of 0.4 will match the OS. However, whatever delay you use (other than 0) is a kludge and not guaranteed to work under every situation, device and memory load, etc.
Instead, you should revisit your approach.
Change your design:
Do not run the risk of encountering this animation race in the first place.
You have a couple of options, including:
Controlling the transition animation yourself and waiting for its completion prior pushing another view controller (using a completion signal or completion block)
Changing your methodology entirely to avoid this conundrum altogether
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff) withObject:nil afterDelay:0.1];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
SecondViewController *viewMe=[self.storyboard instantiateViewControllerWithIdentifier:#"second"];
[self.view.window.rootViewController presentViewController:viewMe animated:NO completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
I have a parent UIView and an UITextView as one of the subviews.
And I created a button to dismiss the parent UIView like this:
-(void)cancelButtonPressed:(UIButton *)sender
{
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.frame = CGRectZero;
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}
I can tell that the parent UIView didn't get released because if I typed some text into the UITextView and dismissed it, when I opened the UIView again, instead of a blank UITextView, the SAME text is in it again.
I checked the Leaks tool but I didn't see any leaking. So I'm guessing if I have some kind of retain cycle or what.
UPDATE:I have another object (which is the AppDelegate) who is holding the UIView's instance: _myView as a global variable like this:
_myView = [[MyView alloc] init];
_myView.nameLabel.text = _user.screen_name;
[_window addSubview:_myView];
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_myView.frame = CGRectZero;
} completion:nil];
But in order to avoid retain cycle, should I create a weak self like this: __weak MyView *weakSelf and in the animation block do this: [weakSelf removeFromSuperview]?
I've also tried calling removeFromSuperview on the view itself, and it doesn't result in the view being released.
If you want to release the view, then go with an approach that uses a delegate. That way, you will be able to call removeFromSuperview on the view, once the animation is complete, and set it to nil. This has worked for me in the past.
So, you can add a method to the view class that you want to animate closed, where you will do the animation. Set your view controller as a delegate to your view, and call some method on the delegate, from the completion block of that animation.
You can create your own protocol for this. If you keep it general enough, and focus only on animation callbacks, you can reuse the protocol in all your view controllers.
Memory management and logic are independent things. A memory leak will never change the behavior of your program. Behavior like displaying something is controlled by what you tell it to display. If it displays the same thing as before, then you must be giving it the same thing to display somehow. Even if you somehow leaked the original thing, if you pass a new thing for it to display, it will display that thing. So look at your logic. Memory management has nothing to do with what you're seeing.
I'm not using a navigation controller or a tab bar controller, I'm not using the push/pop method or presenting views modally. In my main view controller, I am adding a view controller like so:
UIViewController *nextController;
nextController = [[GamePlayViewController alloc] initWithNibName:#"GamePlayView" bundle:nil];
[nextController performSelector:#selector(setDelegate:) withObject:self];
temporaryController = nextController;
[self.view addSubview:nextController.view];
This view controller follows a delegate protocol and when the user is finished in this game view, this code is called:
[delegate backToMenu:self];
which calls this function in the app's main view controller:
- (void)backToMenu:(GamePlayViewController *)sender {
NSLog(#"back to menu");
[temporaryController.view removeFromSuperview];
}
Removing the view with removeFromSuperview seems to get rid of the view only, but I can see due to NSLogging that code is still executing in the .m file of that removed view. The view is still in in the app's memory. It has not been discarded as I had hoped.
"Release" is an old relic never to be used with ARC, so how can I entirely remove the viewController that was created with alloc/initWithNibName?
Thanks!
You should also be using the view controller life cycle methods.
Adding:
GamePlayViewController *nextController = [[GamePlayViewController alloc] initWithNibName:#"GamePlayView" bundle:nil];
nextController.delegate = self;
[self addChildViewController:nextController];
[self.view addSubview:nextController.view];
[nextController didMoveToParentViewController:self];
temporaryController = nextController;
Removing:
[temporaryController didMoveToParentViewController:nil];
[temporaryController.view removeFromSuperview];
[temporaryController removeFromParentViewController];
temporaryController = nil;
Also if temporaryController is a strong property (or you've used an iVar), you should nil it out after removing it.
As the CAAnimation retains its delegate make you remove the animation and nil out the delegate.
-(void)didMoveToParentViewController:(UIViewController *)parentViewController
{
[super didMoveToParentViewController:parentViewController];
if (!parentViewController) {
CAAnimation *animation = [movingObject.layer animationForKey:#"animatePositionX"];
animation.delegate = nil;
[movingObject.layer removeAnimationForKey:#"animatePositionX"];
}
}
If you want to check your view controller is being deallocoated you should implement the dealloc method and place a breakpoint inside of it. I suggest a breakpoint over a NSLog as I don't know how much you already log out so it might get missed, with a breakpoint it is much clearly - actually stopping the program flow.
I have a project in which I'm switching one view with another:
- (IBAction)onClick:(id)sender
{
ViewControllerSecond * sc=[[ViewControllerSecond alloc]initWithNibName:#"ViewControllerSecond" bundle:nil];
[UIView transitionFromView:self.view toView:sc.view duration:3.0
options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
}];
}
I'm using 3 seconds here to make a point. in this second view I have a method to update the GUI that adds another view from a view controller:
-(void)updateGUI
{
sample=[[ViewControllerSample alloc]initWithNibName:#"ViewControllerSample" bundle:nil];
sample.view.frame=CGRectOffset(sample.view.frame, 0, 150);
[self.view addSubview:sample.view];
}
Now, here is the problem: when I'm calling this from the viewDidLoad function - it's working just fine.
However, if called from the viewWillAppear function, the view will appear at the top of the screen and only after the animation has ended will jump to it's position.
How can it be fixed?
Whats wrong putting it in viewDidLoad?
From the looks of it, the order is loading, animating then the delegate methods of appearing. Also put your sample=[[ViewControllerSample alloc]initWithNibName:#"ViewControllerSample" bundle:nil]; in the init part of your Second view controller.
Alternatively, you can call updateGUI before your animation. So before [UIView transition...
[sc updateGUI];
Just call updateGUI in viewDidLoad. Don't do much work in viewWillAppear. viewWillAppear will prevent your view's appear if you make it do too much work.
The answer for that was of two parts:
update when the view is loaded, however this will only happen once, so to re-use the controller I have to call it again.
use the isViewLoaded to see if the view is loaded. if it is, then call the update method.
I am struggling with understanding why the first method below works for hiding and removing a subview of a view. In this first method I pass the pointer by reference. In the second method, which is less general, I have a delegate method designed for removing a specific view. I would like to use the first method, because I have several views that I would like to apply this too. I should mention that the first method works without fail as long as it is called within the implementing class. It fails when I call it from the view controller that I wish to dismiss. I get an EXC_BAD_ACCESS on the removeFromSuperview line when it fails in the first method.
-(void)closeView:(UIViewController **)viewController
{
[UIView transitionWithView:self.view
duration:UINavigationControllerHideShowBarDuration
options:UIViewAnimationOptionCurveLinear
animations:^
{
[[*viewController view] setAlpha:0.0];
}
completion:^(BOOL finished)
{
[[*viewController view] removeFromSuperview];
[*viewController release], *viewController = nil;
}];
}
-(void)closeButtonClicked
{
[delegate closeView:&self];
}
//
// This method works without fail:
//
-(void)closeView
{
[UIView transitionWithView:self.view
duration:UINavigationControllerHideShowBarDuration
options:UIViewAnimationOptionCurveLinear
animations:^
{
// In this context viewController is defined in the class interface
[[viewController view] setAlpha:0.0];
}
completion:^(BOOL finished)
{
[[viewController view] removeFromSuperview];
[viewController release], viewController = nil;
}];
}
-(void)closeButtonClicked
{
[delegate closeView];
}
First of all, it is not according to the style guides, and not a good idea in general, to do a release of the viewController within a method like this. It will get you into trouble quickly. If the caller of this method is responsible for the viewController (it has done the retain), then it should release it as well. This is likely the cause of the first method not working from within the viewcontroller itself.
In the second method you do not pass in the viewController as parameter, which means it needs to be defined in the context.
If you don't release the viewController in this method, then you don't need to set its variable to nil either, and you can simply pass it as normal parameter:
-(void)closeView:(UIViewController *)viewController
{
[UIView transitionWithView:self.view
duration:UINavigationControllerHideShowBarDuration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^
{
[[viewController view] removeFromSuperview];
}
completion:nil];
}
you would then do this at the call-site:
[self closeView:childViewController];
[childViewController release]; childViewController = nil;
It safe to release the child in this way before the animation is done, because the animations block implicitly retains all objects referenced from the block, including the viewController parameter. Therefore, the child's dealloc is not called until the animations block releases it.
This does not work in your first code example, because you pass a pointer to a variable. That is, the animations block does not know it needs to retain the child.
BTW, I am not sure why you want to set the alpha, in the example above I show that you can also remove the view already in the animations block. See more about that in the UIView Class Reference.
**viewcontroller and &self is not the way to go. In Objective-C, you do [self.view removeFromSuperview] in the subview itself, in the parent viewcontroller you do release or with ARC just replace the subview with another view.