UIViewController not releasing subviews when dealloc (using ARC) - iphone

I have what seems like a weird (non?) issue with UIViewController. It appears that the controller is not releasing its subviews when it is dealloc'd. I placed NSLog messages in all of the subview's dealloc method as well as the view controller. The view controller dealloc gets called but the subview's do not. However, if I then push another instance of that view controller on to the navigation stack, it appears that all of the subviews of the previous instance are then released (I get a bunch of NSLog messages in the console letting me know). I've check and I have no separate reference to the custom view controller in the presenting view controller (the one that's doing the pushing).
One small (maybe) detail: The custom view controller does receive a block it stores and then executes before popping. However, I did send nil to it and I get the same behavior. Plus, the presenting view controller does dealloc when popped of the stack, so no retain cycle.
Also, I did try explicitly releasing each view in the dealloc method of the custom view controller. Same behavior.
Is it possible the navigation controller would be holding on to it? It doesn't seem to do this to any of my other view controllers.
My problem is that this does represent a memory leak (of all those subviews); though the leak doesn't stack, it's still a leak.

Ok, this is embarrassing. I did find the problem in another class (called ViewDef) I was inadvertently using as a collection class. It was a quick and dirty way of keeping track of my subviews when I was first figuring out some animations (months ago). ViewDef stored frame/font/color/etc info retrieved from a database, so it was convenient to also store the views when figuring out animations (between orientations). These ViewDefs were being store by my model and passed around, so of course the views were also being retained (and replaced later by another view controller). Anyway, I forgot to insert a warning in my code to fix this later.
Moral of the story: If you plan on doing something stupid, at least document your stupidity so you don't have to broadcast it over the internet later.

you could try setting the subviews to nil in the viewDidUnload method maybe that will help

One thing to try is to make sure all your subviews delegates are set to nil.

Related

Storyboards and memory management

I decided to use storyboards in a project I have been doing. When the app launches it does the right thing of awakeFromNib and then viewDidLoad, but when the app has finished segueing to another view, it doesn't call viewDidUnload and, I think, neither does dealloc. I have used Apple's Instruments and doesn't show any memory leaking.
Just to note, I am using custom segues and testing this by inserting NSLogs into the methods. Has anyone else come across this?
Just want to update: dealloc actually is called but not viewDidUnload.
The viewDidUnload method is solely for the purposes of didReceiveMemoryWarning (i.e. when the view is being removed to recover some memory, but the view controller is not). If you want to see viewDidUnload while running in the Simulator, push or presentViewController to a secondary view, then generate a memory warning from the Simulator's menus. I quote from the UIViewController Class Reference:
When a low-memory condition occurs and the current view controller’s views are not needed, the system may opt to remove those views from memory. [The viewDidUnload method] is called after the view controller’s view has been released and is your chance to perform any final cleanup. If your view controller stores separate references to the view or its subviews, you should use this method to release those references. You can also use this method to remove references to any objects that you created to support the view but that are no longer needed now that the view is gone. You should not use this method to release user data or any other information that cannot be easily recreated.
At the time this method is called, the view property is nil.
viewDidUnload is called when the view is actually unloaded. If you want to clean up your resources when the view is not displayed put that in viewDidDisappear.
If you want to see what is happening with viewDidUnload, run your app in the simulator and from the menubar choose Hardware | Simulate Memory Warning.
Under memory pressure, views that are not on screen are removed and that is when the viewDidUnload method is sent.

Modal View doesn't release itself after dismiss

I've been facing a weird issue with dismissing a modal view.
I present a modal view like this:
ResepiDetail *detail =(ResepiDetail*)[[ResepiDetail alloc]init];
[self presentModalViewController:detail animated:YES];
and dismiss it like this with a back button:
[self dismissModalViewControllerAnimated:YES];
after this the view dismisses itself and goes back to the previous view, but it doesn't release itself from memory. I found it out by sending a notification message and that view received it. Additionally I tried to track the VM memory Allocation, and it seems the view is still in memory.
I'm using ARC and have the same method used for another view which works perfectly.
The code is fine, as posted, so here's some hints on how to proceed:
A sure fire way to be certain your view controller hasn't been deallocated is to override dealloc and log something identifiable. You can still do that in ARC, just don't explicitly call super. If you don't see the log when you expect to, then you have a problem.
Assuming that you have determined that you absolutely do have a problem, then the issue becomes finding the retain cycle. If the issue is that an instance of ResepiController isn't being dealloc'd, then you need to look for...
Any code outside the ResepiController class that has a strong reference to it. For example, if your class signs up as the delegate of some other class, make sure the delegate isn't using a strong reference.
Any internal blocks that may have implicitly retained self. Are there any blocks anywhere in your program that may have a reference to your controller at the time that you think it should be released?

Delegate pattern and UIKit view controllers, what about -viewDidUnload?

I'm pretty new to programming and I started learning ObjC and the CocoaTouch Framework.
I've learnt about the delegation pattern and I'm pretty comfortable using it, but maybe I'm using it too much.
There is one thing I do not understand and it's specific to UIKit view controllers, I've read a lot of posts about it but I'm not able to find a clear answer.
Memory management with delegates?
Why are Objective-C delegates usually given the property assign instead of retain?
Let's suppose that I have a navigation controller and I'm pushing view controllers through it, imagine that the visible controller has as a delegate (assign not retain) a non-visible controller in the stack. A memory warning is incoming and all the view controllers (except the visible one ) are unloaded using the viewDidUnload and dealloc method, the delegate will be unloaded and the "callback" never sent.
If the new view controller is not pushed but presented modally the "connection" between delegate and the view controller is never lost, viewDidUnload is never called in the parent view.
Here is my question:
Is it correct to use delegation pattern between two view controllers?
It is certainly a correct design, depending on your controllers semantics. I used this when having a "master" view controller managing (and receiving delegate calls) from "slave" view controllers.
As to your analysis of what happens in case a memory warning is sent, there is possibly a slight misunderstanding, in that the view controller actually receives the viewDidUnload, but what this means is that the view controlled by it has been unloaded to get back some memory, so the controller can do its part of cleaning (as usual). The view controller is not itself "unloaded" or released or whatever. So the callback will always be sent. The only thing is that if the view had been previously unloaded, you would need to restore it.
Keep also in mind that, if is utterly impossible for you to recreate your unloaded views, you can prevent a specific view from being released by not calling super in your didReceiveMemoryWarning override. Take this suggestion "cum granum salis", however!
In the end, no need to use a Modal View.
Indeed, this is what Apple Docs say:
(void)didReceiveMemoryWarning :
The default implementation of this method checks to see if the view controller can safely release its view. This is possible if the view itself does not have a superview and can be reloaded either from a nib file or using a custom loadView method. If the view can be released, this method releases it and calls the viewDidUnload method.
(void)viewDidUnload:
This method is called as a counterpart to the viewDidLoad method. It is called during low-memory conditions when the view controller needs to release its view and any objects associated with that view to free up memory. Because view controllers often store references to views and other view-related objects, you should use this method to relinquish ownership in those objects so that the memory for them can be reclaimed. You should do this only for objects that you can easily recreate later, either in your viewDidLoad method or from other parts of your application. You should not use this method to release user data or any other information that cannot be easily recreated.
Your UIViewController is fairly lightweight and should never be unloaded in a low memory condition. The UIView owned by the view controller is very heavyweight and will definitely be unloaded in low memory conditions. Your controller should be ready to recreate the view if necessary, but you'll never need to regenerate your stack of view controllers.

popToRootViewControllerAnimated question

I've got next question - why my app dont waiting when i call
[navController popToRootViewControllerAnimated:NO];
... set new viewcontrollers here
I want:
1/ Kill all viewcontrollers by call popToRootViewControllerAnimated - with NO parameter - then i think i can immediately set new view controllers
2/ set new view controllers
But in my logs i see next:
call poptorootview controller
code after poptorootview
dealloc of views and controller (because i call poptorootview)
Why ? How can I detect that all views is killed and navigation controller is poping to root ?
Thanks,
A popped view controller will eventually be released (unless another object retains it), but the reference does not specify exactly when and how it is released. I wouldn't be surprised if it is not immediately released; the UIKit objects may use the view controller for the transition animation. It may be autoreleased not released, which may explain your logs. Also, I wouldn't be surprised inside UINavigationController multiple objects are retaining view controllers in the stack at the same time. Concisely speaking, there is no documented behavior about releasing popped view controllers. All we can be sure of is that it will be released at some point, as otherwise it will lead a memory leak.
Therefore you don't know when the view controllers are actually deallocated. Even if you find out it is subject to change without a notice. However, you can be sure when the view disappeared, using UINavigationController's delegate methods.

ViewWillDisappear versus dealloc

I put an NSlog in my dealloc method of my view controller. It does not get consistently called. I do notice that ViewWillDisappear does get called always. Would it be ok to move all my tidy upcode here? Setting stuff to Nil and release calls.
Anybody got some advice on why dealloc is not getting called? I know it says in docs it may not get called, but if you have a very simple App it gets called always. So something i do must be affecting the dealloc.
This is the code that calls my ViewController than isnt always calling my dealloc.
-(IBAction) playComputerTapped:(id)sender
{
PlayGameViewController *pgvc = [[PlayGameViewController alloc]
initWithNibName:#"PlayGameViewController" bundle:[NSBundle mainBundle]];
pgvc.gameMode = 1;
[self presentModalViewController:pgvc animated:YES];
[pgvc release];
}
The above code takes me from the mailmenu ViewController into the game.
Below is the code to leave the gameViewController and take me back to the menu.
[self.parentViewController dismissModalViewControllerAnimated:YES];
Thanks
-Code
Don't you mean viewDidUnload instead of viewWillDisappear ?
viewWillDisappear is called when the view controller is disappearing. This usually happens when the view controller is being popped out, or other view controller is pushed to the stack. Purpose of viewWillDisappear is to stop active actions - for example stop animations, hide some elements or similar.
viewDidUnload is probably what you meant as this one is called when view controller's view is unloaded. This should never happen for a view controller that is currently visible, only for controllers that are somewhere in the navigation stack (part of UITabBarController or UINavigationController), but not currently visible. Purpose of viewDidUnload is to release any UI elements that are part of the view and that the view controller retained as well.
To understand it's important to realize the reasons why a view for such controller would want to be unloaded. Reason is memory consumption. Views consume considerable amount of memory, even when they are not currently visible. Views are usually very easy to reconstruct - simply by calling the code that constructed them in the first place. That's especially very easy if you are using Interface Builder. So these views are best candidates to be freed to gain more memory.
When system doesn't have enough memory it starts calling didReceiveMemoryWarning method of view controllers which are not currently visible. If you have created your controllers from template in Xcode, this method simply calls super's (UIViewControllers's) implementation [super didReceiveMemoryWarning]. That default implementation will release self.view, which in turn should deallocate it together with all its subviews.
Now let's say that your view-controller needs access to some of the subviews to manipulate it in some way. For example you can have a UILabel element there and you want to change its content accordingly. To access this element you create a member variable (IBOutlet) and connect it to the element. Now your view-controller owns that label, so its retain-count is increased. When controller's view is released, so is the label, but because your view-controller still retains the label, it will not be deallocated. Therefore you should release the label in viewDidUnload method.
I've seen applications that were creating views programmatically (in loadView method), but loading was done in such dirty way that it was not possible to reconstruct the view after it was once deallocated. Therefore each time system was out of memory, it had called didReceiveMemoryWarning which in turn deallocated the view, but after navigating back to that view-controller application had crashed. A fast "bugfix" was to remove calling [super didReceiveMemoryWarning] in view-controllers. Well, system didn't get the memory and some strange effects occurred, but at least the application didn't crash immediately.
Now the third one - dealloc. This is called when object is not owned by anyone and its memory is going to be freed. Here you need to release all objects that you have retained. For view-controllers those are usually references to model classes.
I want to describe one more possible scenario. Let's say you have a view-controller displaying a chat with another person. Let's say it's very fancy chat, with emoticons and buddy-icons being animated. Let's say that each chat-entry is displayed as a cell of UITableView.
When your buddy sends you a message, you want to append a new cell into table-view by reloading it. Therefore your view-controller has an outlet to the table-view.
In viewWillDisappear you should stop the animations of emoticons and icons.
In viewDidUnload you should release the table-view.
In dealloc you want to release chat's history (probably NSArray of all messages sent and received during this conversation).
Now if you navigate away from your chat, viewWillDisappear gets called and you stop the animations.
When system is low on memory, and your view-controller is not visible, didReceiveMemoryWarning gets called and the view is released. Your viewDidUnload gets called and you release UITableView so that it can be really deallocated.
When you navigate back to the chat, loadView gets called again and your view is constructed again, viewDidLoad is called after that. Your model (representation of chat conversation) is still there, so the table-view's datasource has all the data as before, so table-view will display exactly the same thing as before the view was deallocated. After all viewWill/DidAppear is called where you start the animations.
When you finish chatting with your friend, you release the view controller, and it gets deallocated - dealloc is called, you release an array containing the chat messages, and cleanup everything else.
I hope it make things a little clearer.
Well that depends.
If you need to reduce the memory footprint of your app, unloading stuff while the view is not visible via viewWillDisappear is a good way to go. However you'll need to re-initalize everything once the view will be shown again, depending on its content this may produce considerable overhead - maybe even without the need.
For a small app (in terms of memory usage), using dealloc to unload your stuff is fine. It will be called only if the objects retain count drops to zero and is the last method that will be run before the object is destroyed. When using autorelease, that may not be the case right away and of course the object could be retained by some object other than the parentviewcontroller, preventing it from being destroyed and thus dealloc from being called.
You might want to check out the Memory Management Programming Guide, it'll explain things more detailed.