I have two UIViewControllers, VC1 and VC2. VC1 has a button that invokes a triggered Modal segue to VC2. This segue is defined in the storyboard. In VC2, the user can return to VC1 using a pan gesture that executes this line of code:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
This code has worked for a long time. However, all of a sudden when this line runs, the app crashes. I enabled zombie objects and I can see this error:
-[VC2 retain]: message sent to deallocated instance 0x7f843a81e200
I've run the zombie profiler and here are the results:
UIClassSwapper initWithCoder seems to be where the app crashes, and there appears to be a an over retained object there, but I can't get any further. When I set an exception breakpoint, I just get into the assembly code, and this isn't of any apparent help.
So, there are a couple of questions here. The first is simply what am I doing wrong in how I am presenting and dismissing this view controller? After that, I don't know how to crack into the event history to figure out how to handle this zombie object.
Any help is appreciated. Thank you!
It seems that you use ARC project. Please provide more code:
1. How you initialize presentingViewController property (and declare). Is it (nonatomic,strong)?
2. How do you put this ViewController to the screen. Is it a kind of modal view controller etc?
-- added after comment.
ARC based project decides automatically if the object is needed or not and removes it from the stack automatically. So, the only one useful recipe to avoid this problem is to add NSNotification observer and post there notifications time after time. It will keep view controller in the stack and avoid automatic release.
Another solution - check if the controller is still in the stack:
if (!self.presentingViewController) [[THECONTROLLER alloc] init];
The third one - is to use some object to declare VC1 and VC2 as class variables, and present them as modal view controller when needed. In this case, object and its variables will be kept untouched as long as any of VCs presented.
In any case, the Exception happens because ARC considers that VC is not needed anymore and releases it atomatically.
Related
I have a LoginViewController (UIViewController) that when all the criteria is met and the user hits the Login button, a storyboard segue is run that pushes the ProfileViewController (UIViewController). When this happens, I have a log statement in my LoginViewController's dealloc method to see if it is called and to my disappointment it is never called. My question is whether or not it is supposed to be called? Also, when I log in, sometimes I get a "Received memory warning" and sometimes I do not which I find strange because I am taking the exact same steps in both cases and yet i get a memory warning one time and not with the other.
Anyone can shine some light on this that would be great!
Thanks.
UINavigationController maintains a stack of view controllers. You start with one element, a LoginViewController, on that stack. When you push a ProfileViewController, you now have two elements on the stack. The LoginViewController can't be deallocated until it is removed from the stack.
If you want the ProfileViewController to replace the LoginViewController on the navigation controller's stack, you can write a custom segue class to implement that behavior. See this Q&A.
(You might think you could use the “Replace” or “Show Detail (e.g. Replace)” segue type in your storyboard, but those only work if you are using a UISplitViewController.)
With ARC enabled, when a object is not referenced, it will be released.
In order to display view from ProfileViewController, you instantiate a object of it in LoginViewController, and that's how you still can see the profile view after it is presented. If LoginViewController instance is released, the profile view will also get released(assume no one else references it). For the same reason, the LoginViewController instance is not released because another object is holding a reference to it. Say your views are presented in Window -> ProfileViewController -> ProfileViewController, it's the window that keeps ProfileViewController instance from being released.
If you have two views as I assumed so far, the memory warning should be from somewhere else. Two views cannot cause the issue.
I'm pushing and popping ViewControllers in UINavigationController.
I'm tracking the memory consumption of my app.
While pushing the new viewController the memory consumption is increasing gradually, but when I'm popping the same ViewController using [self.navigationController popViewControllerAnimated:NO]; the memory consumption does not decrease but the constant.
That particular viewController can be pushed and popped by user many times which can lead the high memory consumption of app in RAM.
What should I do to optimise my memory consumption?
When you dismiss a view controller (or pop it), it will be deallocated if you didn't make any strong pointers to it (that controller is retained by the navigation controller, or the presenting view controller, so you usually don't need to have a pointer to it when you create it and push or present it).
It will be be released if there are no other strong pointers to it
Try to avoid using strong properties for IBOutlets.
Consider reviewing whether you are referencing self in a block. If you do, you risk holding onto the UIViewController reference after you have popped it.
For a more in-depth review of why, check out this answer:
How do I avoid capturing self in blocks when implementing an API?
If your app design allows the user to push and pop the same view controller over and over again, you may want to look at reusing the same view controller and just updating its contents each time it's pushed.
Instead of creating and destroying it over and over, create one, set up its contents and push, when it's popped, keep it around ready to be shown again. Next time it needs to be shown, update its contents and then push it again.
I would like to say, that my last few days were spent on searching the web for my app memory problem. I was switching between 2 UIViewControllers. One of them had a scroll view which kept all subviews on it. It turned out that that UIVC loads a new scroll view without releasing the previous one. It took me several hours to realize it.
What I did was:
Looking for any kind of deadlocks inside the app, then searching for every variable that had a strong atributte and other desperate measures. But what really worked was:
#IBAction func backBB(sender: UIBarButtonItem) {
collectionView.removeFromSuperview()
self.frontView.removeFromSuperview()
eventsPhotos.removeAll(keepCapacity: false)
symbolContainerView.removeFromSuperview()
self.myScrollView.removeFromSuperview()
dismissViewControllerAnimated(true, completion: {})
}
I manually removed some views and contents. I've done it in "Back" button but you can do this in other methods like viewWillDisappear(animated: Bool).
Once I made this, my allocation chart in the developer instruments showed the memory allocation going up and down... And it was solved...
I think you get an error, when you try to pop the view controller because the navigation controller does not have a valid reference to the view controller, as it was released after you pushed it.
Nil the popover on dismiss.
[menuPopup_ dismissPopoverAnimated:YES];
menuPopup_ = nil;
Make sure your viewcontroller (A) has no reference of any other viewcontroller (B) or any object it has. Incase it has then make sure that VC-B is not referencing back VC-A. If it has some reference back to VC-A then make it a weak property. Otherwise strong retain cycle will keep the VC in memory even if its popped.
Another thing is to check wether theres any closure in your VC, check its body if it is referencing any property or method with self then make a Capture list to avoid retain cycle as "Closures are Reference Types"
Check if theres any NSNotification observer you are not releasing
Make a print call in deinit method to check wether its deallocated.
For further understanding on memory management:
https://medium.com/mackmobile/avoiding-retain-cycles-in-swift-7b08d50fe3ef
https://www.youtube.com/watch?v=GIy-qnGLpHU
You should use an unwind segue instead of popping.
There's a crash in the app that I'm working on that I'm having a difficult time tracking down the cause of. Here is the only set of events which causes the crash:
The app opens with the main view controller (VCmain) being presented. A button is triggered which opens a modal view controller (VCmodalA) via a segue. VCmodalA simply displays some information gathered about a core data object. VCmodalA is then dismissed by a button press. At some future point another view controller is presented modally (VCmodalB), which has some fields for the creation of a new core data object. After the object is created, if VCmodalB is dismissed, the app crashes with the following error:
*** -[UILabel _supportsContentDimensionVariables]: message sent to deallocated instance 0x818e200
If VCmodalA is not displayed prior to VCmodalB, or a new core data object is not created, or VCmodalB is not dismissed, then no crash occurs. I am at a loss what '_supportsContentDimensionVariables' means or who is sending it, although it appears to be sent to a label which was on VCmodalB (therefore the crash must be caused because a call is being made to a label that was deallocated when the view controller was dismissed).
I've spent hours poking around in Instruments looking at the Zombie left behind and trying to isolate the offending code by commenting it out, but I have been totally unsuccessful. At this point any hints would be welcome!!
Thanks so much!
There are two top causes of these kinds of errors: failure to use ARC, and direct access of ivars (particularly if you're not using ARC). Fixing those two issues is the best way to avoid these kinds of crashes.
As for how to debug it, first, you want to audit your accesses to UILabel objects. If you have any ivars that point to UILabel they should be strong or weak, never assign.
You should make sure that view controllers don't run code when they're not onscreen. This crash makes me think that this is a likely problem. For example, do not setup timers in viewDidLoad or initWithFrame:. Set them up in viewDidAppear: and tear them down in viewWillDisappear:. Similarly for KVO or delegation. View controllers manage views; if they're doing something when their view is not onscreen, your design is incorrect.
I am making simple storyboard application which has 2 UIViewControllers and I am switching between them by using modal segue. Each UIViewController has one UIButton which is used to perform segue to another UIViewController. In viewDidLoad method I animate appearance of that UIButton on each UIViewController. I am using Cross Dissolve Modal segue.
When I press UIButton on 1st UIViewController I navigate to second UIViewController and animation is executed and 2nd UIViewController is shown. After I press UIButton on 2nd UIView Controller, first UIViewController is shown and it's animation is executed. Everything looks fine and viewDidLoad methods are called for each UIViewController when ever I navigate to it. And that's great.
I tried now to change Modal segue type from Cross Dissolve to other two by default offered in XCode Interface Builder. I changed to Cover Vertical, and everything worked just fine, without changes. But when I tried Flip Horizontal Modal segue, I saw a problem.
When performing Flip Horizontal Modal segue, my UIButton on both UIViewControllers is shown, but animation isn't executed. I tried debugging and I am sure that animation commands are being executed, but animation isn't shown.
So that's my first question: Does anyone know is there any difference between these types of Modal segues which may cause my animation not showing up?
Other questions are related to basic theory of segues and memory management. When I perform segue and navigate to some UIViewController, viewDidLoad method is called every time. So, does that mean I created new object instance each time viewDidLoad method was executed?
I also notice that viewDidUnload method is never called. So, if answer to previous question is affirmative (each viewDidLoad execution creates new object instance), does that mean that my UIViewController object instances are never being unloaded and deleted? Or ARC is doing garbage collection behind the scenes?
If someone could explain how things works with storyboard segues and memory management/object lifecycle and why viewDidUnload method is never being called, I'd be very grateful.
[edit #1: Trying to unload UIViewController after performing modal segue]
[update #1: This shouldn't be done, viewDidUnload will be called automatically]
I am making segue in IBAction attached to UIButton click. I have written this peace of code to perform modal segue.
#try
{
[self performSegueWithIdentifier:segueToPerform sender:self];
}
#catch (NSException *exception)
{
NSLog(#"Exception: %#", exception);
}
#finally
{
[self viewDidUnload];
}
I have manually called viewDidUnload in #finally block and I have checked weather viewDidUnload is called in runtime and yes - it is called.
Does this mean I managed to unload my UIViewController object I created when navigating to it with modal segue from another UIViewController and remove it from memory?
Is this method regular as a replacement for:
[self dismissViewControllerAnimated:YES completion:nil];
because this above line returns me to UIViewController from which I navigated to current UIViewController, but that doesn't fit my needs, because I need to perform new segues from current UIViewController to other UIViewControllers (beside returning back to UIViewController from which I navigated to current one)?
[edit #2: Finish]
At the end I changed implementation model and loaded new UIViews under single UIViewController after I created separate XIB files for those UIViews. I have marked answer from #dasblinkenlight as the right one since it contains lots of useful informations and discussion on that answer gives good answers to some doubts about using modal segues.
I do not know the answer to the first part of your question, but once you learn the answer to the second part, I am sure that you would go with a different solution anyway.
viewDidLoad method is called every time. So, does that mean I created new object instance each time viewDidLoad method was executed?
Absolutely. "Modal" segue causes the new view to obscure the old one completely until the new view is closed. If you go back and forth many times, your code will accumulate a whole "stack" of views underneath the current one.
I also notice that viewDidUnload method is never called. So, if answer to previous question is affirmative (each viewDidLoad execution creates new object instance), does that mean that my UIViewController object instances are never being unloaded and deleted?
This is correct, all the view controllers that you create are still there, ready for you to close the views on top of it.
Or ARC is doing garbage collection behind the scenes?
ARC is not a garbage collector, it is a reference counting mechanism with a little automation from the compiler. The objects are still there.
You should change your code to call
[self dismissModalViewControllerAnimated:YES];
in the second controller, rather than using a modal segue that brings you back to the first one.
I hit a similar issue where I had a complex VC navigation web, and the resulting use of segues (no UINavigationController) was sucking up too much memory.
You may want to take a look at this question/answer to see my final solution.
The conversation here between uerceg and dasblinkenlight helped me in finding this solution.
Segues and clearing historical ViewControllers from memory
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.