Memory not released in ios view hierarchy - ios

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).

Related

ViewController not being deallocated due to internal retain cycle

Some of my view controllers don't get deallocated after being popped from view. I've gotten rid of the other strong references so I'm left with this internal retain cycle held through a reference form _externalObjectsTableForViewLoading. It's a private UIViewController property so I'm unable to clear it myself. I don't know if iOS has an API to clear it or why it's not being cleared after popping the view controller.
I've tested with with my app running in Release mode both in iOS 11 and 12. Running the app in Instruments renders the same stairs pattern seen in Xcode with the view controllers being retained.
Any ideas? Thanks in advance!
In your problem, is one viewController accessing another viewController? Our problem is that there was a non weak reference to a callback in another viewController.
As mentioned in your and in other posts on this, _externalObjectsTableForViewLoading is a viewController private property, but a storyboard related property. This leads me to think that your code has strong references to another object that is a view controller through a callback or through a direct property reference to its instance.

Managing instances of programmatically instantiated view controllers

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.

Does manually setting NavicationController.viewControllers release the existing viewcontrollers?

I have code that replaces an entire stack of ViewControllers in a NavigationController, as sort of a "reset" that switches between a number of different threads of UI scenes. Instead of creating a different NavigationController for each flow in storyboard, I decided to just change the NavigationController.viewControllers array manually:
self.navController.viewControllers = [vc1, vc2]
What happens to the old stack of ViewControllers? Do they automatically get release/unloaded? or do they hang around forever and therefore causing memory leak?
Note:
I know about unwind segue, but I didn't want to use that since even if I unwound to the first VC in NavVC, I still have to replace it with a different VC, and setting .viewController is the only way I know to not result in a "Back button"
What happens to the old stack of ViewControllers?
The viewControllers property points to an array and has copy semantics, so when you assign an array of view controllers to that property, the navigation controller copies the array. When you assign a different array to that property, the previous one will be deallocated (assuming there are no other strong references to it). The same is true for any of the view controllers in the array -- if nothing else has a strong reference to them, they will be deallocated. This is no different from any other Objective-C object.
After you set the new view controllers, your UINavigationController will lose its strong reference to the old stack. If you do not have any remaining strong references to the items in the old stack, they will indeed be released by ARC.
One way to verify this behavior is to override -(void)dealloc in the view controllers you want released, and put some logging in there. you should be able to see when dealloc is called.

iOS Singleton memory leak for property

I have singleton. My singleton has UIViewController property. When I push some view controller I set pushed view controller to the singleton property.
For example I pushed B view controller from A view controller
and inside B view controller initialization code i set the property of singleton:
inside init code:
Singleton *singleton = [Singleton sharedInstance];
singleton.viewController = self;
This code means even when I pop back to previous controller A the instance B never be destroyed as I think and seems I will have memory leak.
So each time when I will push B controller I will increase memory usage.
How can solve it. I have tried use weak instead of strong for singleton property but I am not sure that is the solution.
The expected way for me - something like cascade destroying. But maybe I confused and this code will not cause memory leak. What do you think.
Not really. You aren't leaking the instance because you still have a reference to it. And the memory usage doesn't increase because next time you push B it will set itself to the singleton and replace the previous instance (which will then be deallocated).
Generally, if you do want to store the reference, you should make it 'weak' and / or have the view controller remove itself when it is removed from its parent.

ARC ViewController gets deallocated

I have a view controller that displays a button. When I click the button, the corresponding selector needs to be called. However, with ARC, the application crashed with an EXC_BAD_ACCESS message.
-(IBAction)reseauPushed:(id)sender{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self.viewCenter addSubview:self.reseauVC.view];
}
with
#property (strong, nonatomic) ReseauVCIpad *reseauVC;
and the crash log :
-[ReseauVCIpad performSelector:withObject:withObject:]: message sent to deallocated instance
I use ARC.
The button action :
-(IBAction)helloPushed:(id)sender{
NSLog("hello);
}
This ReseauVCIpad view controller is obviously getting deallocated some how. Either you accidentally are setting self.reseauVC to nil somewhere, or, more likely, the parent view controller, itself, is somehow getting deallocated. Is there any chance you did this addSubview technique for any of the preceding view controllers? And if not, how did you instantiate the root view controller?
To diagnose where the problem is, I'd suggest you add dealloc methods to all of your various view controllers so that you can confirm if any are getting deallocated prematurely. (Either set breakpoints or put in NSLog statements.) I'd wager you're seeing the parent of ReseauVCIpad getting deallocated, which is, in turn, allowing ReseauVCIpad itself to be deallocated.
By the way, as others have pointed out, the typical answer to this problem is to make sure you're doing the appropriate containment calls (show below), or if this child view controller takes up the whole screen, you should just be pushing to it or modally presenting it. Clearly, you have a strong reference to ReseauVCIpad, so the lack of containment calls isn't the source of the problem with ReseauVCIpad itself (though I wonder if you are doing this addSubview trick without containment calls with one or more of ReseauVCIpad's parent view controllers).
But you still should be doing these containment calls (or do a proper modal/push transition), regardless, to ensure your view controller hierarchy stays in sync with your view hierarchy (see WWDC 2011 video Implementing UIViewController Containment for lengthy discussion of why this is important). The appropriate containment calls for adding a subview with its own controller is, at a minimum, as follows:
- (IBAction)reseauPushed:(id)sender
{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self addChildViewController:self.reseauVC];
[self.viewCenter addSubview:self.reseauVC.view];
[self.reseauVC didMoveToParentViewController:self];
}
For a more detailed description, see the video I referenced above, or see the Creating Custom Container View Controllers section of the View Controller Programming Guide for iOS.
And when you want to remove it, you should do the appropriate containment calls there, too:
- (void)removeReseau
{
[self.reseauVC willMoveToParentViewController:nil];
[self.reseauVC.view removeFromSuperview];
[self.reseauVC removeFromParentViewController];
self.reseau = nil;
}
This generally solves this issue (where the child view controller was deallocated). It won't solve your reseauVC problem (because you already have strong reference), but (a) you should do this wherever you do addSubview with a view controller, regardless; and (b) I show you the pattern in case you're doing addSubview elsewhere without maintaining a strong reference.
Several things. You should not add one view controller's content view to another view controller unless you set up a parent/child view controller relationship. This was added in iOS 5, and expanded in iOS 6 (and probably further expanded in iOS 7...) Look at methods like addChildViewController:, removeFromParentViewController, isMovingToParentViewController, and didMoveToParentViewController:
The easiest way to set up a parent/child view controller relationship is to use storyboards (which requires iOS 5) and an embed segue. (Which I believe was added in iOS 6). That takes care of all the housekeeping for setting up parent/child view controller relationships for you.
Your second view controller is being saved to a strong property, so I'm not clear on why it's being deallocated. I'm also not clear where the call to performSelector:withObject:withObject: is coming from. What source line is crashing, and are you using performSelector:withObject:withObject: in your code anywhere?

Resources