If I instantiate a ViewController from my storyboard programatically, will its memory be freed once it's no longer be shown in the application?
I'm showing it as a modal.
Expanding on #Schemetrical's answer, you need to make sure there is at least one strong reference to your VC or it will be deallocated immediately.
This is a crash in the making:
func viewDidLoad()
{
childVC = self.storyboard.instantiateViewControllerWithIdentifier("childVC")
self.view.addSubview(childVC.view)
}
In the above example the current VC's content view keeps ownership of the newly created view, but nobody keeps ownership of the view controller. It gets deallocated as soon as the function returns, and the first time something tries to reference the now-deallocated VC, you crash (Say there is a button who's action points to the VC.)
If you push your VC onto the navigation stack, the navigation controller takes ownership. As soon as it's popped off the stack, it gets deallocated. If you present your VC modally, the system takes ownership for as long as it's on screen. As soon as it'd dismissed it gets deallocated.
If you want a VC to stick around after it's popped/dismissed, you need to keep a strong reference to it somewhere. You might save a reference to it in your app delegate, in a singleton, or in your app's root view controller.
As long as nothing holds strongly on the vc, it will dealloc. Once you dismiss that vc, the view releases its reference on the object and since there are no references, it will dealloc.
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.
I have an iOS-App which uses ARC. I don't use InterfaceBuilder, all UI is generated manually. In that app I have several UIViewControllers with SubViewControllers. Those ViewControllers a tied together from a menu (-ViewController) who pushes them on the stack.
My problem is, that memory doesn't get freed when switching between the ViewControllers.
Is it wrong to keep references to the SubViewControllers like this?
#property (nonatomic, strong) UIViewController subViewController1;
#property (nonatomic, strong) UIViewController subViewController2;
viewDidUnload never gets called. Has anyone a good example how to build a clean view hierarchy?
By assigning the view controllers which get pushed on the stack to a strong instance variable / property, they will not be deallocated when popped off of the stack. The strong properties are holding on to the pushed view controllers even after they are popped off the stack, so they never get to a state where they can be deallocated.
Normally I do something like the following when pushing a next-level-down view controller onto the navigation stack:
SLSMoleculeSearchViewController *searchViewController = [[SLSMoleculeSearchViewController alloc] initWithStyle:UITableViewStylePlain];
[self.navigationController pushViewController:searchViewController animated:YES];
Under ARC, the new view controller will be allocated and will be retained on creation. When pushed onto the navigation stack, it will be retained once by the navigation controller. Because this new view controller is not referred to after being pushed, and is not assigned to a strong property or instance variable, ARC will release it after the second line.
Remember, it's still being retained by the navigation controller, so it's still live in memory. However, once the navigation controller pops it off the stack this view controller will be released. Since nothing is holding on to it at that point, it will be deallocated as expected.
If for some reason you need to maintain a reference to this sub view controller in your higher-level view controller, you could use a weak property or __weak instance variable. This will not hold on to the view controller, and will turn to nil once the controller is deallocated.
weak references are only supported for applications that target iOS 5.0, though, so you won't be able to do this for anything that needs to run on iOS 4.0. The 4.0-compatible unsafe_unretained property is not something I would recommend in this case, due to the danger of a pointer to deallocated memory.
This most likely has nothing to do with ARC. viewDidUnload is only called on a view controller when the view property is released / set to nil and this typically only happens if the app receives a memory warning.
Try triggering a memory warning in the simulator and see if that causes your viewDidUnload method to fire. If it does then everything is fine. If not, you are probably over-retaining your views somehow, perhaps by assigning them to other strongly retained properties.
There are exceptions to the view-retaining policy, for example the UINavigationController frees up views in its view controller stack if they aren't frontmost, but it does that by simply setting the view of its child controllers to nil when they're covered by another controller's view.
If you want your views to be released when they aren't onscreen, either set the controller's view property to nil in the viewDidDisappear: method, or stop retaining the view controllers when their views aren't onscreen and just create fresh controller instances each time you need to display them (that way both the controller and view will be released when not in use).
I am new to iOS and wondering how to properly implement view controller unloading/reloading.
Right now my app has a NavigationController with a MainMenuViewController (custom view controller) set up as root view controller. During the course of app lifetime, new ViewControllers are pushed/popped on the Navigation Controller. This works fine, the appropriate ViewControllers are initiated (from NIBs) the first time they are pushed to the stack.
However, I now want to unload one particular ViewController when it is popped, then reload it automatically when it is pushed again.
I have added a [self release] to that ViewControllers viewDidDisappear: and it unloads, but when i try to push the view again, i get a message sent to dealloc'ed instance error and crash. Therefore, my questions are:
Is that a proper way to unload a popped ViewController?
How to check if a given ViewController is loaded or not?
How to force a reload? With loadWithNib:, then push onto navigation stack?
Regards,
Peter
Welcome to iOS programming. Your crash is a memory management issue. It may take you a bit to get the hang of it but memory management gets way easier if you just follow one rule:
an object needs to release anything it retains (alloc is equivalent to retain)
In this case, your view controller is releasing itself and it definitely did not retain itself. Here's how the sequence works with a navigation controller:
The navigation controller is initialized with a root view controller (the first one on its stack). Lets call this firstViewController
A user action tells firstViewController to initialize secondViewController and push it onto the navigation controller. In most cases, firstViewController will release the instance of secondViewController after pushing it. At this point, firstVC is done with secondVC. The navigation controller is now retaining secondVC
User touches the back button on the navigation bar of secondVC. The navigation controller will pop secondVC from the stack and release it. As long as no other object is retaining it, secondVC will get be dealloc'ed.
Now user is back in firstVC. They can do the same user action which will init and push a new instance of secondVC.
Hope that helps a bit.
I'd also recommend you (re)read the Apple docs and look at the sample code referenced in the framework docs.
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
I need to save my data by calling a method I already have when a viewController is popped using the back button created by the UINavigationController.
Is there a way to get a delegate callback or a notification I didn't see anything in the documentation?
In your viewWillDisappear method, you can check the property:
[self isMovingFromParentViewController]
to find out if the view is disappearing as a result of being popped off the stack or not.
You will be notified that the view will be disappearing, with the view controller method viewWillDisappear:, however, this will be called each time the view is moved offscreen, whether that means the controller is popped or a new controller is pushed, or whatever else may cause your view to disappear.
Perhaps a better design would be to save your data in your controllers dealloc method. Normally, a navigation controller is the owner of a view pushed into it's stack, so popping it usually causes it to deallocate. This isn't always the case though and depends on how you've written your app.