I have a MyViewController, it's based on UIViewController, and I used it like the following code:
MyViewController *nextViewController = [[MyViewController alloc] init];
[self.navigationController pushViewController:nextViewController animated:YES];
[nextViewController release];
And in the MyViewController, with a user event, have the following code:
[self.navigationController popViewControllerAnimated:YES];
Now, I find that, the MyViewController's dealloc don't be called, but, when I switch the App to background, for example, pass the home button, the dealloc method has been called! This a big problem! There will be got a lot of MyViewController wouldn't be release, when user go to a MyViewController, and go back, again and again, and just, the lots of memory could be release only when the App goto background.
So, can anyone help me about this, thanks!
The obvious reason is that something is retaining your viewController. You will have to look closely at your code. Do you do anything that in your class that uses delegates, since they sometimes retain the delegate. NSURLConnection will retain your class, and so does NSTimer. You can scatter code in you class and log your class's retain count, and try to find out where. In the code you showed so far the retain could should just be 1, since the class is only retained by the navigation controller.
Also, before you pop your view, get a reference to it, pop it with NO animation, and then send it some message that has it report the retain count (this would be some new method you write). That new method could also log other things, like whether it has any timers going, NSURLConnections, etc.
Related
I'm new to programming and have been developing an iOS app over the last couple months. To me the app looks like its functionally really close to done but I hit an issue today that I think might be a bigger underlying problem.
When I dismissViewController in a navigation controller and go back to the view later it seems to still have the same values. I thought when I do a dismiss that view is destroyed and a new one created later. I've been trying to read about it and I think maybe its a memory cycle thing, the view is kept in memory because there are objects in the view that still have pointers? Is there some general rules on how to handle this? Should I be setting object to nil any time I leave a view controller?How to I make sure I'm not keeping unnecessary things in memory?
If you create your view controller in the following manner, creating it and then pushing it(commented out line) or presenting it, then it is guaranteed that the ViewController will always have an initial state as defined by your initializtion code.
- (IBAction)showViewController: (UIButton *)sender {
MyViewController *vc = [[MyViewController alloc] init];
[self presentViewController: vc animated: YES completion: nil];
//[self.navigationController pushViewController:vc animated:YES]
}
check the viewController you dismiss,if the properties has strong reference(strong,retain) point to parentViewController.
This problem sounds quite basic but I don’t understand what I am overlooking.
I am trying to push a new view controller into a navigation controller, however the topViewController remains unaffected.
#import "TNPViewController.h"
#interface TNCViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#implementation TNCViewController
-(void)userDidSelectNewsNotification:(NSNotification*)note
{
TNPViewController *nextViewController = [[TNPViewController alloc] init];
[[self navigationController] pushViewController:nextViewController animated:YES];
UIViewController *test = [[self navigationController] topViewController];
}
The test shows an instance of TNCViewController instead of TNPViewController. How is this possible?
UPDATE
Thanks for everyone's participation. The method name indicating notifications is a red herring. I found the problem, as Stuart had mentioned previously but deleted later on. (As I have high reputation score, I still can see his deleted post).
My initial unit test was this:
-(void)testSelectingNewsPushesNewViewController
{
[viewController userDidSelectNewsNotification:nil];
UIViewController *currentTopVC = navController.topViewController;
XCTAssertFalse([currentTopVC isEqual:viewController], #"New viewcontroller should be pushed onto the stack.");
XCTAssertTrue([currentTopVC isKindOfClass:[TNPViewController class]], #"New vc should be a TNPViewController");
}
And it failed. Then I set a breakpoint and tried the test instance above and it still was showing the wrong topviewcontroller.
At least the unit test works if I change
[[self navigationController] pushViewController:nextViewController animated:YES];
to
[[self navigationController] pushViewController:nextViewController animated:NO];
A better solution is to use an ANIMATED constant for unit tests to disable the animations.
This doesn't really answer your question about why your navigationController is not pushing your VC. But it is a suggestion about another possible approach.
You could instead add a new VC on the Storyboard and simply activate the segue when the userDidSelectNewsNotification method is activated. Then change the information accordingly to the event in the VC, specially since you are initializing it every time anyway.
This is something of a stab in the dark, but the issue is hard to diagnose without more information.
I see you're trying to push the new view controller in response to a notification. Are you sure this notification is being handled on the main thread? UI methods such as pushing new view controllers will fail (or at least behave unpredictably) when not performed on the main thread. This may also go some way to explaining the odd behaviour of topViewController returning an unexpected view controller instance.*
Ideally, you should guarantee these notifications are posted on the main thread, so they will be received on that same thread. If you cannot guarantee this (for example if you're not responsible for posting the notifications elsewhere in your code), then you should dispatch any UI-related code to the main thread:
- (void)userDidSelectNewsNotification:(NSNotification *)note
{
dispatch_async(dispatch_get_main_queue(), ^{
TNPViewController *nextViewController = [[TNPViewController alloc] initWithNibName:#"TNPViewController" bundle:nil];
[self.navigationController pushViewController:nextViewController animated:YES];
});
}
Also, it appears you are not initialising TNPViewController using the designated initialiser (unless in your subclass you are overriding init and calling through to initWithNibName:bundle: from there?). I wouldn't expect this to cause the transition to fail entirely, but may result in your view controller not being properly initialised.
In general, you might be better creating your view controllers in a storyboard and using segues to perform your navigation transitions, as #Joze suggests in his answer. You can still initiate these storyboard segues in code (e.g. in response to your notification) with performSegueWithIdentifier:, but again, be sure to do so on the main thread. See Using View Controllers in Your App for more details on this approach.
*I originally wrote an answer trying to explain the unexpected topViewController value as being a result of deferred animated transitions. While it is true that animated transitions are deferred, this does not prevent topViewController from being set to the new view controller immediately.
I have a simple scenario.
I push myViewController onto navigation stack.
myViewController is basically showing a collection view over entire screen. I added an additional UIPanGestureRecognizer on this collection view and set myViewController as its delegate. I am retaining a strong reference to that pan gesture recognizer inside myViewController.
When I tap Back, myViewController gets popped from the navigation stack and deallocated. The myViewController's dealloc method gets called as it should. Up to this point everything works as expected.
Then I try to open the same myViewController like the first time and the crash occurs with the message:
[MyViewController gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:]: message sent to deallocated instance
I have this method implemented in myViewController and it always returns YES. But this shouldn't even matter because no one should even be calling this method because none should have a strong reference to it. Obviously someone is still holding a weak reference since the dealloc method was called on the only instance that ever existed.
Not even the init method of the MyViewController gets called.
I tried to put the following code both in dealloc and in viewWillDisappear:
[self.myPanGestureRecognizer removeTarget:self action:#selector(panGestureAction:)];
[self.collectionView removeGestureRecognizer:self.myPanGestureRecognizer];
self.myPanGestureRecognizer.delegate = nil;
self.myPanGestureRecognizer = nil;
But, it didn't change anything. Every time the same thing - myViewController gets initialized and displayed normally the first time. The second time I try to initialize and push, the exception occurs. Obviously, it is related to the pan gesture recognizer that I added, but I don't see how.
The answer to this question ended up fixing my issue that was very similar:
gestureRecognizer shouldReceiveTouch persisting in deallocated view causing crash
I was incorrectly setting self.navigationController.interactivePopGestureRecognizer.delegate to self.
So even though the error reported from the NSZombie was in another class. It's gesture recognizer was not actually the culprit, it was my interactivePopGestureRecognizer.
I'm a bit weak in my Objective C I'll admit, my ultimate goal is pass data from ViewController3 back to ViewController1. Actually, that part is already done and successful. However when calling [self.navigationController popToRootViewControllerAnimated:YES] I get EXC_BAD_ACCESS.
ViewController1 <ViewController2Delegate>
- (void) didAddEventLocation:(Event *)event {
NSLog(#"Event name = %#", event.name); //Shows name successfully
}
ViewController2 <ViewController3Delegate>
- (void) didAddEvent:(Event *)event {
[self.delegate didAddEventLocation:event];
}
ViewController3
[self.delegate didAddEvent:event];
[self.navigationController popToRootViewControllerAnimated:YES];
Sorry for the poorly formatted code, just trying to simplify. Doing [self.navigationController popViewControllerAnimated:YES] has no problem, however it only takes me to ViewController2. I know I'm doing something very wrong here, but can't quite place my finger on how to resolve it. Let me know if I need to clarify.
Use Zombies to hunt down what is giving you the EXC_BAD_ACCESS. Some object has been released and is now being called on when you are popping back to the root view controller.
Try this link:
How do I set up NSZombieEnabled in Xcode 4?
Looks like, that one of your you controllers(first in my opinion) is deallocated. In VC3 method check that self.navigationController exists. Then you have to check all his VCs. I think that nothing holding first VC. Problem may be solved by using(for example) addChildViewController method of your navigation controller, or if smth will have a reference to your controllers.
Also, you can use NSNotificationCenter to send some information from one instance to another if you have problems with path between them.
HTH!
I have a viewController called "FirstViewController". In an IBAction i call another ViewController called "thePageFlipViewController" and push it in sight via
[self presentModalViewController:thePageFlipViewController animated:YES];
after some time the user closes thePageFlipViewController with a button where the following code is executed via a delegate in FirstViewController:
[self dismissModalViewControllerAnimated:YES];
[thePageFlipViewController release];
And here is my problem:
-viewDidLoadin FirstViewController get's sometimes called after dismissing thePageFlipController. I don't understand why, because firstViewController should live in background. Is it dependent how long the modal view is displayed? is it possible that ARC does release something?
My problem is, that i initialise a lot of objects in viewDidLoad and the app crashes if viewDidLoad gets called again. I define some Routes for RESTKit there and RestKit complains that the routes are already set up and crash the app.
Any Help is appreciated.
When a view is not actually displayed it can be unloaded to free up memory. You would get a call to viewDidUnload: when that happens so you can release any objects you are holding strong references to. Then next time the view is needed, viewDidLoad: will get called again when the view is reloaded, there you have to recreate the objects you released in viewDidUnload:.
See the Memory Management section of the UIViewController class reference.
Also this answer has a good explanation already.