Managing Memory Usage VIewController Stack - ios

I've noticed when opening multiple instances of a view, my memory continues to climb with the more views that the user opens. If the user starts to hit back the memory usage drops with each view controller closing. However, depending on whatever the user is doing he can open 20+ view controllers, how can I manage the memory utilization? Keep in mind I need all those views loaded in the background so they can be quickly loaded when the user hits back
Heres how I'm creating each instance:
let storyboard = UIStoryboard(name: "Storyboard", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("FriendPage") as! FriendVC
self.navigationController!.pushViewController(vc, animated:false)
How can I manage the memory utilization?

A navigation stack keeps all the view controllers loaded in memory. That's integral to the way it works.
As Mr. Beardsley says, you can set up your view controllers to free their large data structures in your viewDidDisappear method (including setting image views to nil) and then reload them in viewWillAppear. If you make sure everything is cached to disk it should reload quickly.
To go beyond that, you'd need to forgo a navigation controller and create your own parent view controller that displayed a series of child view controllers. You could make the parent keep track of the navigation path the user followed and save state data for each view controller to disk, and then on the user pressing the back button, re-invoke the previous view controller and reconstitute it from it's saved state data. As long as everything is loaded from disk and not from the network you should be able to get near instant display of each screen when the user presses the back button.
This would require a fair amount of custom work on your part but shouldn't be that hard.
There are methods like 'transitionFromViewController:toViewController:duration:options:animations:completion:' that let you create custom transitions between child view controllers. You should be able to easily create whatever transition effect you want.
By saving a list of the view controllers that the user visited and a block of state data needed to recreate each view controller from disk you should be able to simulate a navigation stack while only having one child view controller active and in memory at a time.
Before going down this path, though, I would suggest looking at your user interface and seeing if there is a way to limit the depth to which the user can navigate. You could add some sort of limit to the depth of the user's navigation. The details would depend on your app design.

Unless you are running into memory pressure issues, I would not worry about it. How large does your memory usage grow when you navigate 20 levels deep in your view controllers? If you are running into issues, then you would have to save the state of the previous view controllers to persistent storage, and then set your current view controller as the root. When you go back, you'd have to reinstantiate the view controller and restore the state.
A middle of the road approach might be to have view controllers release any image, or other large binary data, when a new controller is added to the stack. When you navigated back, the view controller would have to reload the data from disk or the network.

Related

Viewcontroller stack understanding issue

I got some serious problems understanding the viewcontroller stack.
When will my app use a stack to save the previous viewcontrollers? Only if I use a navigation viewcontroller or anytime I use normal viewcontrollers and segue modally between them?
So I was just wondering if I use some sort of chained routine for example, like going from vc 1 to vc 2 and from vc 2 back to vc 1. No navigation controller, just modal segues, no unwinding.
Does my app got performance issues because of a stack (which will grow everytime I go around) or doesn't it make any difference?
----updated
So basicly this is my problem. If I went through the routine of the app, the views get stacked everytime I do a transtition.
UINavigationController will retain any controller you push onto it's navigation stack until you pop it back off.
Any UIViewController will retain a controller it presents modally until that child controller is dismissed.
In either case every controller will at a minimum consume some memory until you remove it. Apps which construct ever expanding stacks of controllers are likely to encounter a number of issues including:
you will eventually run out of memory, how fast depends on how much memory each controller uses.
you may see unexpected side effects if many controllers in the background react to the same event.
users may become confused if they change state in an instance of controller 'A', push an instance of controller 'B' on top of it, and then "return" to a second instance of 'A' added to the top of the state. Since they're looking at a new controller and view whatever selection, scroll position, user input, or other state they set on the previous instance may be lost.
developers, including you, may come to dread touching this app.
I suspect that everyone will have a better experience if you view controller management matches whatever visual metaphor you are presenting to the user.

Is there a way to check if too many VC are stacked

I wonder if there is a way to check if I have too many views stacked in my app.
My app design is:
Navigation controller -> table view (table view works like root VC)
From the table view I can open the menu VC as a modal segue, and form there I can open the login VC as a modal segue, if I log in I end up on the account page like:
Navigation controller -> table view -> menu -> login -> account page
From the account page I can go deeper:
Navigation controller -> table view -> menu -> login -> account page -> list settings page -> edit settings page
Now I have 7 VC's stacked if I count the navigation controller, even though two of them are displayed as modal VC's.
My app does not crash but is this a good way to do it? If I understand it right apps now have to share CPU when running split screen on ipad, so I am not sure if this way is eating too much memory.
Or should I simply make the account VC become the new root VC and reset the stack? And when going back to the table view make that one the new root VC again.
View Controllers are light weight objects. If your concern is memory you should react to memory warnings. E.g. in the viewcontrollers' didReceiveMemoryWarning method. You could release any cached images, remove views if the VC is not visible. Releasing cached objects and images is not always desirable in viewDidDisappear as the user might come back to the screen and you want to avoid reloading everything if there are no memory issues. didReceiveMemoryWarning is the correct place to help the system in freeing memory without sacrificing user experience.
You can of course check how many view controllers are on a navigation controllers stack by checking the UINavigationController's property viewControllers.count.
Resetting the stack to always only have one root viewcontroller is nice but usually quite complex. It is also not really possible inside a navigation controller.
You should use Instruments to check your memory consumption and to verify that you correctly react to memory warnings. Memory warnings can be manually triggered in the simulator.
So when views are stacked on a navigation controller, they are not fully de-allocated from memory, even when they are offscreen. There isn't a strict maximum number of stacked views but you should be smart about it.
If you are concerned about RAM usage then be sure to utilise viewWillAppear and viewWillDisappear to their full potential, performing cleanup operations such as nulling off gesture recognisers and observers, and stopping any listeners that may be running on a background thread. This will reduce the amount of memory your views are using when offscreen; and is good coding practice anyway.
Hope this helps somewhat.
EDIT: Felix also raises a good point about memory warnings, if iOS is concerned about the memory usage of your app it will issue a memory warning which you can react to in the way felix explains.

IOS Push many view controller without pop

I wanted to know what will happen if we keep pushing same view controllers again n again. I have 4 buttons, each of which triggers a View. All the 4 buttons are there in all the 4 views.So each time I click a button a view is loaded. So I am pushing a view controller. will this lead some kind of memory management issue or nay other issue? Any other way to handle this? I cant use Tab bar cause of design issues.
I WANT TO BASICALLY IMPLEMENT A TAB BAR WITH 4 BUTTONS. I CANT USE A TAB BAR DUE TO DESIGN ISSUES
This won't lead to any specific memory management issue. Every time you allocate an object it takes up some memory. When you push the view controller you are just allocating a new copy of that object. Whether it's a view controller or a data model or a string, each object takes up some memory. You can easily profile how much additional memory gets used each time you push the view controller, but most likely it is negligible (probably much less than 1kb depending on how much you have in there). I just profiled one of my view controllers and it used 320 bytes. So for simple math, let's say each push takes up 1kb of memory. And an iPhone 5 has 1Gb of RAM. That's enough to hold about 1 million view controllers. So I wouldn't worry about it.
However, if you want to worry about it then you should implement didReceiveMemoryWarning in your view controller and release any unneeded objects.
You can add back btn from your 4 other controllers. On back its removed from navigation stack memory is reclaimed .
If you still have to manual way of managing it, you can use [self.navigationController viewControllers] to check if viewController is there or not and take appropriate decision.

Pattern for preloading data for subsequent view to be displayed

Very simple use case: Let's say an iOS app displays a MovieListController view (inside of a UINavigationController) with a list of movies. When the user touches on one, the app pushes a MovieDetailController onto the navigation stack (i.e. [[MovieDetailController alloc] initWithMovieId:(NSString *). In the MovieDetailController's viewDidAppear: method, it makes an HTTP call to retrieve details based on the movie ID passed into it.
The challenge is that the MovieDetailController gets pushed onto the navigation stack right away, and for a second or two while the details haven't been retrieved, the view shows a bunch of blank fields, which is undesirable.
To get around this, I'm thinking of having the MovieListController not push the MovieDetailController onto the stack right away. Instead, it would put up a progress indicator (I'm using SVProgressHUD), then call MovieDetailController's initWithMovieId: method which would kick off the HTTP call. Then when the data is received, the MovieDetailController would make a callback back to MovieListController to remove the progress indicator and then push the MovieDetailController onto the navigation stack.
Is there a better pattern for this type of scenario? Should I be considering having the MovieDetailController push itself onto the navigation stack when it's ready?
Note: I have considered loading the detail view and putting up an activity indicator, but you'll still be able to see an 'empty view' behind it which looks a bit weird. I have also considered just having the MovieListController retrieve the details itself but this seems to break the encapsulation model - the MovieListController should just be concerned about listing movies, not about their details.
Any thoughts? This Movie stuff is just an example - looking for a general pattern here.
Personally I would take the following approach.
User selects the movie they want details for
Push to the detail view and rather than showing your skeleton view with empty fields, overlay a loading view, you could continue using your progress HUD on top of this for any animations you gain with that.
Once the results come down, remove your HUD and the loading overlay view that is hiding all the data/fields
The reason I would go this route rather than showing the HUD before pushing the view controller is that you can give the user the opportunity to cancel their selection. I am not familiar with SVProgressHUD but hopefully when a HUD is displayed you can enable touches, specifically the user touching Back on your UINavigationController in the event they accidentally selected the movie or the request is just taking longer than they are willing to wait for.
It also separates the logic form your list view, your detail view is standalone and could be initialized anywhere in your app (maybe you want to cross link similar movies within a movie detail view) and you do not need to rewrite the logic of the presenting view waiting on the results to come back.
In this situation, I personally would return to the model-view-controller pattern.
There are several system apps that display a detail view from a list of objects, e.g. Calendar, Contacts, etc. Presumably, as with EKEvent and ABPerson in their respective apps, the main view controller maintains a list of the model objects. When the user selects one of the items, the main view controller passes the selected model object to the detail view controller. The detail view controller itself doesn't have to do any data loading. So, like #ChrisWagner said, we want to separate the logic from the view controller.
Method
Similarly, you might want to use a MovieList class that stores an array of Movie objects. Each Movie stores the values for all the fields in the detail view controller - essentially, all the information the app needs about the movie. For example, you might have a property NSString *movieTitle, or NSDate *premiereDate. The movieTitle would be set by the MovieList at initialization because it's just metadata; on the other hand, the premiereDate might be nil if the data hasn't loaded, so you would have a property BOOL isLoaded to check for this condition.
You could then proceed in one of two ways:
1) Say the main view controller wants to push a detail view controller for a movie. Then the main view controller would dig the appropriate Movie out of the MovieList and check if it's loaded. If not, it would call something like -(void)loadContents on the Movie. When the model object is finished loading, it will post a notification that it's finished loading. At this point the main view controller will dismiss its progress view and push the detail view. If you use (1), it's not as important to use a MovieList coordinator.
2) If you want to be more aggressive about loading the movie information, you could implement a method on MovieList that calls loadContents on its Movies in the background. Then there's a higher chance that a movie will already be loaded when the user selects it.
Edit: Note that if you decide to use a MovieList type object, only the main view controller should be allowed to access this list. In a way, the model structure I've described parallels the view controller structure. The detail view controller doesn't know about the list view controller, so it shouldn't know about the MovieList either.
Benefit
The benefit of using this Movie and MovieList structure is that the data model is completely separated from the view controllers. That way, the user is free to cancel, select, present and dismiss view controllers while the data is loading. (Imagine if the user pressed Back to dismiss the progress HUD, canceling any HTTP data it would have gathered. Then, if he decides to come back to that same movie, the new detail view controller has to start loading afresh.) Also, if you decide to replace view controller classes in the development process, you won't have to copy and paste a bunch of data-handling code.
I think you'll find that structuring your app in this way not only gives you and the user more freedom, it also makes your app efficient and open to later extensions. (Sorry to post so late, but I wanted to include MVC in this pattern-related discussion!)
I think a more advanced route would be to start the detail requests as the cells are being being shown on in the tableview/collection view. Then as the cells move offscreen, cancel the requests. You might be loading movie details unnecessarily, but I don't think this is a big deal, because you'd only be filling your db with movie details you might not need and coredata can handle that. This is assuming that your api requests are happening on a bg thread and they don't affect the main(UI) thread.

tabbar memory management

a tab bar based app with 5 tabs switching option...how i am suppose to manage memory efficiently?
switching between tab are very frequent i am how to manage this scenario?
or
all tab will remain active no matter what? but thats a bad option...
please describe in detail regarding tab bar memory management
Let UIKit handle it. You shouldn't worry. UIKit will unload views as it sees fit (and you get told about that in viewDidUnload of your view controllers).
So for instance:
You start on tab 1. Tab 1 is the only view controller whose view will be loaded.
You tap on tab 2. Now tab 2's view controller will be loaded and tab 1's view controller is still around.
More time goes on, you tap on other tabs which loads other view controllers.
UIKit notices that memory is running a bit low or it just wants a bit of a tidy up (you have no control over this). So now it will go and unload some of the view controllers' views (but obviously never the one you're currently viewing).
You should of course be a good citizen and release anything you hold onto in your view controller in viewDidUnload that you can easily create again when the view wants to be loaded again.
You don't need to worry about it, unless you're in a low memory situation, in which case the view controllers' views may be released, and you just need to correctly implement:
didReceiveMemoryWarning
and
viewDidUnload
See Apple docs here and here for details.
All View Controllers associated with a Tab Bar Controller are retained by the tab bar controller, but if you're using intense amounts of memory, you can release objects or resources used by your view controller when viewWillDisappear: or viewDidDisappear: gets called. And recreate / reallocate those memory hogging objects when viewWillAppear: or viewDidAppear: get called when the user clicks it into view again.

Resources