I have developed an iphone application that opens to a tabbed view with the first tab being a uinavigationcontroller. Within that controller is a uiviewcontroller that contains a uitableview. There are 2 items listed in the tableview. When I select one or the other item, it displays yet another uiviewcontroller with dynamically generated uiviews.
When I press the "Back" button at the top of the navigation control, to return to the previous uiviewcontroller (that contains the tableview), and then I select 1 of the 2 items in the uitableview again, it eats up almost 2M of memory according to Instruments. This occurs each time, until it reaches about 24M, and my application crashes.
I am registering no leaks whatsoever.
Is there something I need to be doing when the "Back" button is pressed to release the memory allocated to the uiviewcontroller.
I'm not sure how far you are in iPhone development, or how much you know about the memory management, but it could be a reference counting issue. Remember: If you call alloc or retain, you need to call release, and never call release on something you haven't alloc'd or retained.
The navigation controller retains all view controllers pushed onto its stack, so if you ensure that such view controllers are autoreleased or that you otherwise have no claim on them (e.g., alloc, push, release), they will automatically be released when popped.
If you're doing this and you're still losing memory, perhaps you are over-retaining the your custom views from their view controllers?
It's difficult to say without seeing code, but one thing that might be useful is implementing -didReceiveMemoryWarning on all your UIViewControllers and logging details of them -- then if you see a memory warning from a view controller you think should have been deallocated, you have a starting point for further investigation.
Also, have you tried the Clang Static Analyzer? The Leaks tool is useful, but gives plenty of false negatives. The CSA is no panacea either, but it catches some things Leaks misses.
Related
I'm working on a note app that has a collection view of notes, and taping each cell will lead the user to different note. In the case of my app, each note is essentially a PKCavnasView that inherent from ScrollView.
What I found is that when user navigate back to collection view and select a new cell, the memory used by the previous note is not released. So as user navigate between different notes, the memory usage will soar up.
What's more of a problem is that when the app goes to the background, all the memory is still not released.
What I'm wondering is that, will the navigation controller automatically release the memory of each tap to different cell? If not, is the autorelease pool the solution here? If so, where should I put the autorelease pool.
My Initial View Controller of the storyboard load another view controller using performSegue:withIdentifier method which in turn loads some other controller using same performSegue:withIdentifier method.
However, neither the initial view controller nor the second view controller are deallocating. They both tend to have a reference count of 1 as seen via instruments.
I need to send user back to first controller when he logs out of application. The only way I have figured so far is to use performSegue:withIdentifier method and send the user back to initial controller.
However, it concerns me that previous controllers will not have been deallocated thus, resulting in re-creation same view controllers.
Since I need to logout a user back to first screen, I want to make sure that all previous view controllers have been deallocated.
When you perform a push or modal segue, it will not (and should not) release the view controller from which you're seguing. It needs to keep it so that when you pop/dismiss back to it, it will still be there. The exception to this rule is when using a split view controller and you use a replace segue. But that's a special case.
If you want to go back to the first scene, if you're using a navigation controller and using only push segues, you can use popToRootViewControllerAnimated. (For iOS 5 targets, I'll always use navigation controller, and hide the navigation bar if I don't want it visible, for that reason. It's convenient to be able to pop back multiple levels. It's cumbersome to achieve the same effect with modal segues.) In iOS 6, you can use an unwind segue, in which you can pop/dismiss back an arbitrary number level of scenes, for example, to return to your initial scene.
Looping with performSegue is not a good idea..
If you have to go back in your VC hierarchy, you should either use a UINavigationController with pushing/poping VCs, or presenting/dismissing a modal VC. You can combine both by modally presenting a UINavigationController.
Prior to iOS 6 A UIViewController will stay alive but its more expensive UIView will be deallocated to save memory. The UIViewController itself is pretty light compared to a UIView.
Since iOS 6 you should according to the documentation override didReceiveMemoryWarning
Docs for UIViewController:
Memory Management
Memory is a critical resource in iOS, and view controllers provide
built-in support for reducing their memory footprint at critical
times. The UIViewController class provides some automatic handling of
low-memory conditions through its didReceiveMemoryWarning method,
which releases unneeded memory.
Prior to iOS 6, when a low-memory warning occurred, the
UIViewController class purged its views if it knew it could reload or
recreate them again later. If this happens, it also calls the
viewWillUnload and viewDidUnload methods to give your code a chance to
relinquish ownership of any objects that are associated with your view
hierarchy, including objects loaded from the nib file, objects created
in your viewDidLoad method, and objects created lazily at runtime and
added to the view hierarchy. On iOS 6, views are never purged and
these methods are never called. If your view controller needs to
perform specific tasks when memory is low, it should override the
didReceiveMemoryWarning method.
As long as you manage you correctly react (depends on the iOS Version) and dealloc the view I see no problems here.
I'm thinking of a way to respond to memory warnings generated by iOS. I got one "expensive" tab bar controller that is a good candidate for de-allocation in response to memory warning.
How would I go about marking a controller managed by UITabBar "free for deletion", but allow the user to re-initialize the controller if a user re-selects the proper UITabBar tab?
Will UITabBar take care of re-initializing a controller that was deallocated, or will this involve some sort of lazy initialization?
Do I need to write custom code for what happens when a tab is selected and the controller is nil?
UITabBarController auto manage its allocation and deallocation.
For example, when you add a UIViewController to it, the tabbarcontroller will NOT load your view (calling viewDidLoad), it will load only your class (init method). Then you can do all the heavy work on viewDidLoad.
When it receive an warning notification, TabbarController will release all your views, except the current one and will reload when user go back to a released tab.
--
sry for the bad english there.
Suppose I have 2 views. In the first view, I allocate memory to displaying many UI components such as an UILabel, UIImages, etc.
Suppose the user navigates to the next view (via UINavigationController)
Is it OK to deallocate memory assigned to displaying UI components in the first view and then initialize them again once the user goes back to the first view (in viewFirstLoad or the appropriate function)?
It seems to me if you don't do this, then memory will keep on increasing the longer the user uses your app in that particular session.
Is this not allowed? frowned upon? impossible?
It is perfectly normal and in fact, that functionality is built in standard UIViewController - when controller is not displayed its view may be released from memory and you can release all its subviews (e.g. retained through IBOutlet references) in controller's -viewDidUnload method.
When controller needs to display again it reloads its view again.
It depends. Generally, the rule of thumb is that you should free objects that you don't need. If your view is just a view, then yes, I'd release it and all of its subviews. If your view has data that was obtained through a lengthy retrieval process (e.g. a web service call), I'd probably hold onto the data somewhere so that I don't have to go back out and retrieve it when the user goes back to the first view.
To clarify a little: Apple recommends you display data specific to a view in it's -viewDidLoad method, such as setting text on labels. Then, in -viewDidUnload you should release (or nil outlets of) the view objects you setup in -viewDidLoad. It's critical you implement -viewDidLoad, as the base UIViewController code checks that it's subclass actually implements -viewDidLoad before it assumes it can unload the view (and therefore call -viewDidUnload). Failing to implement -viewDidLoad results in the controller thinking it can't recreate your view at a later time, and so it doesn't unload the view from memory. A developer I know experienced this same problem, took forever to track down.
I'm getting strange navigation bar behaviour, for example when I hit back button the screen displayed is the previous screen, however the Navigation Bar items do change. So I'm left with screen A, but with nav bar buttons for screen B.
Could this be due to memory leaks? I do note with my app still:
This behavior seems to happen:
immediately if I trigger memory
warning via the simulator menu, or
on a device after it has been on
for a while [without being killed
and then restarted as an app].
I do have some memory leaks I'm
trying to clean up (i.e. Profiler
highlights items in "leaked blocks"
section)
Any tips on fault finding root cause of why pushing a back button would end up in a weird state? e.g. screen on previous parent view, but nav bar items don't change...
UPDATE - I have finally removed the memory leaks in my app, however I note the nav bar issue still remains. I guess this doesn't confirm the answer to my question is NO in general, but in my specific case it wasn't the memork leak...
From Apple:
The navigation controller updates the
navigation bar each time the top view
controller changes. Thus, these
changes occur each time a view
controller is pushed onto the stack or
popped from it. When you animate a
push or pop operation, the navigation
controller similarly animates the
change in navigation bar content.
Based on this, I would start by looking for a bug or misconfiguration in your view definitions. Check for any InterfaceBuilder warnings if you defined your views via NIBs. Make sure your view hierarchies are correct in both UIViewControllers. Also check for possible bugs in your view life-cycle methods: viewWillAppear:, viewWillDisappear:, etc,.
Actually, it would be nice if you could post some screenshots and/or code. Thanks!
Any view that is not currently visible and only retained by it's view controller (as a part of the view property of that view controller) will be released (along with any non retained subviews) when a memory warning occurs.
Chances are you are creating the view as a part of init, and not retaining it in the controller (simply letting the view socket hold it from releasing). One way around this is to create properties for the views you create (nonatomic, retain), and after creating them and autoreleasing, assign to those properties, don't forget to assign those properties to nil as a part of dealloc to avoid leaking. Another way is to create your custom view elements in viewDidLoad as opposed to init.
Hard to say without code example from the offending views :)
I've seen something like this happen after calling -[UINavigationController setViewControllers:]. You might try not doing any programatic manipulation of the navigation controller's view controller beyond calling -[UINavigationController pushViewController:animated:].