I'm having problems with my application receiving low memory warnings while the user is deep within a navigation controller stack of views. After the user browses through a bunch of hierarchical options in subsequent UITableViews, he can open a PDF document in a UIWebView (in a different view controller).
Everything works fine, the PDF loads and the user can flip through the pages. However, when the document is a bit large, or has several pages, and the user taps on the "Back" button in the navigation controller, he app crashes as the previous view controller in the navigation controller stack has been deallocated.
After searching around for ways to deal with low memory warnings, and dealing with this type of problem, I found several posts that just advise people to release the objects that can be released, and then lazy-load them later on when the user tries to load a view that has been deallocated. One of these posts is Craig Hockenberry's Dealing with memory loss: the cleanup post.
While that's a bit helpful, it doesn't give me much information to work from.
Can someone provide a simple guide on how to deal with low memory warnings, and how to implement "lazy loading" of objects?
When memory is low, the system sends out a series of memory-related messages. Any instantiated view controllers will get the -didReceiveMemoryWarning message. If you don't implement this, the default action (assuming the view controller is not front-most) is to release the controller's view member. If you've got hooks into it, or perhaps into its subviews, that could cause issues when your controller gets returned to the top of the stack.
Your first task is to figure out what exactly is causing the problem. Which object is being deallocated? Usually, fixing this is simply a matter of making sure the object is retained properly.
I suggest you use NSZombiesEnabled to try and track down the object you're having trouble with. Once you have that, you can make sure you're retaining it properly.
Related
I have an app that has many web services and notifications going on. I need a logout feature. I wish there was a way simply to kill the app and restart it but there is not. Does anyone have some recommended guidelines on creating a logout function (it will take the user back to the login screen). The problem is there are notifs that should be unsubscribed from, views that should be removed, view controllers that I want to be released, then everything to reinitialize. That seems like a lot of work for a simple task. Any thoughts?
The first thing to make sure when terminating all requests is to change all delegates that are supposed to receive responses to nil. After you've taken care of this, you should indeed remove all irrelevant view controller's views from your root view (and release them if they are retained anywhere), and of course flush any existing data you don't need. If you design your MVC elegantly, you can achieve these actions without a lot of fuss, for example a ScreenManager Singleton class that manages all your view controllers should have no problem taking you back to the login screen while releasing any other view. A DataManager Singleton class that holds various data collections should have no problem removing any unneeded data entities and so on...
I am introducing auto-rotation to my app and I'm having an issue with a memory warning. Whatever orientation I start my app in, as long as the device remains in that orientation, I get no memory warnings. However, the first time I rotate the device the following warning is placed on the console: Safari got memory level warning, killing all documents except active. When this happens all view controllers, other than the one be viewed, are unloaded - this produces unexpected behaviors when navigating back to view controllers that should normally already be on the stack. The app never crashes and this warning occurs once upon the first rotation, after that it never happens (until I stop/start the app again). Also, this only happens on the device - no memory warning when running in simulator.
Has anyone seen this behavior? In any case, does anyone have any suggestions on what I might try in order to remove the memory warning.
Thanks in advance.
You can't assume that memory warnings will never happen; you have to handle them gracefully. Suggestions:
Check for memory leaks with Leaks (note that it doesn't catch all leaks).
Fix your view controllers to handle a view reload. Specifically (unless you override -(void)loadView), it'll call -(void)viewDidUnload on a memory warning and -(void)viewDidLoad when it becomes visible again. You might fix this by saving state in the view controller and restoring it to the views in -(void)viewDidLoad.
If you can't be bothered handling the memory warning, implement -(void)didReceiveMemoryWarning and do not super-call (i.e. comment out [super didReceiveMemoryWarning]). This is lazy, and might cause a crash if your app ends up using too much memory (background apps like Safari and Phone will be killed first).
You can test memory warning behaviour with the "simulate memory warning" option in the simulator.
Memory warnings are part of a normal iOS behavior, due to its limited memory, especially now that multi-tasking is supported.
UIKit doesn’t only allow navigation back from a view controller, but also allows navigation to other view controllers from existing ones. In such a case, a new UIViewController will be allocated, and then loaded into view. The old view controller will go off-screen and becomes inactive, but still owns many objects – some in custom properties and variables and others in the view property/hierarchy. And so does the new visible view controller, in regard to its view objects.
Due to the limited amount of memory of mobile devices, owning the two sets of objects – one in the off-screen view controller and another in the on-screen view controller – might be too much to handle. If UIKit deems it necessary, it can reclaim some of the off-screen view controller’s memory, which is not shown anyway; UIKit knows which view controller is on-screen and which is off-screen, as after all, it is the one managing them (when you call presentModalViewController:animated: or dismissModalViewControllerAnimated:). So, every time it feels pressured, UIKit generates a memory warning, which unloads and releases your off-screen view from the view hierarchy, then call your custom viewDidUnload method for you to do the same for your properties and variables. UIKit releases self.view automatically, allowing us then to manually release our variables and properties in our viewDidUnload code. It does so for all off-screen view controllers.
When the system is running out of memory, it fires a didReceiveMemoryWarning. Off-screen views will be reclaimed and released upon memory warning, but your on-screen view will not get released – it is visible and needed. In case your class owns a lot of memory, such as caches, images, or the like, didReceiveMemoryWarning is where you should purge them, even if they are on-screen; otherwise, your app might be terminated for glutting system resources. You need to override this method to make sure you clean up your memory; just remember you call [super didReceiveMemoryWarning];.
An even more elaborate explanation is available here: http://myok12.wordpress.com/2010/11/30/custom-uiviewcontrollers-their-views-and-their-memory-management/
My Rootviewcontroller uses NSURLConnection to get data from a server, and then, based on this data, loads a bunch (like 7) of smaller UIViewControllers that each also use their own NSURLConnection to get some more specific data from the server. But, the problem is, only the RooTViewController is recieving callbacks from:
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
the other UIViewControllers never get callbacks...
You should really work with the assumption that only one view controller is active at the time. So if you want the other view controllers to do work, even when they are not visible on the screen, then you should move that logic into some singleton object that does all the network communication. The viewcontroller, when they appear, will simply ask this object for the data.
For the iPhone it is a really bad design to let inactive view controllers do stuff in the background. The only thing they do is manage the view that you currently see on the screen.
#St3fan is correct about good patterns for UIViewController. Many a developer has been burned thinking his UIViewController always has a non-nil view (it doesn't).... Even so, I'll discuss here what may be going wrong.
There are two likely causes: you're not setting the UIViewControllers as the delegates of their NSURLConnections, or you're not actually starting the NSURLConnections. The most likely cause of the latter is that you're using NIBs and expecting them to load at the beginning of the program, when they actually only load when needed.
As #deanWombourne notes, you can have all the NSURLConnections you want (**). Walk through your code in the debugger. Make sure you're not sending messages to nil. When "nothing happens" it almost always means you're sending messages to nil.
(**) Don't get crazy with connections on iPhone. Network connections are expensive to create (in both time and battery). If you can get all the data you need over a single connection, there are some advantages to doing so. This doesn't mean you should bend over backwards to avoid connections; just be careful of letting them get out of control.
In a multi-view application, do you think its better to let views automatically get unloaded in memory tight situations, or to actually only ever have one View Controller allocated at a time, and when you switch views, the new one is created, the old one removed, the new one adde d and the old one released. Deallocating every time also means that there is a slight delay when switching to a new tab (very slight). So What do you think?
Also, I'm a bit confused about how and when and where and by who views are released (through viewDidUnload) automatically. If someone could clarify that for me, thanks.
In general, don't unload views unless you have to (didReceiveMemoryWarning) or it makes sense (something like a login form that's unlikely going to be used again).
You can't really assume that you have a fixed amount of memory. iPhone's have less memory than iPod touches. iPhone 3GS's have more memory than either. Jail-broken handsets often have substantially less memory.
By only releasing views when you have to you're making your app run faster on the 3GS and allowing it to run when there's less memory available.
The didReceiveMemoryWarning method releases the view if it is not visible. The following is from the documentation (v3.x):
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.
Obviously you also need to release any cached data. SDK2.x does not have the viewDidUnload method.
Depends, if you have a bunch of Views that the user switch back on forth from often then I would say to keep those in memory at that time, if theres views where the user wont come back to for awhile then you can probably unload that viewController and save memory. However if you have views that are taking up a ton of memory each then it might be wise to unload the viewcontroller when its not in use. Its really a matter of how often you are going back to the views and how much memory the view takes, also how many views you have. Taking these things into consideration you should be able to make a good decision of when to keep the viewControllers around and when the unload them. I believe that the view is a round as long as the ViewController is around (unless u release it explictly, which might have bad side effects (dont know)) , viewdidUnload just tells you that the view was unloaded of the screen, not too sure on that point tho.
I already had a question like this, but I already deleted it anyway.
I have very simple app that has a root view controller and it switches between two other view controller views. So in my root view controller, It lazy loads the instances of the two other view controllers. Each time the switch button in the toolbar is pressed, the current view controller being displayed (its view) is unloaded (set to nil), and the new one is loaded and added to the subview.
Since I load my view controllers and unload at specific times, the lazy loading code being in the getters is very confusing because I don't actually want to load them right when I use them, I need to load them before so the flip animation will look good. So I think I want to make loadFirstVC and loadSecondVC methods to load the view controllers. Is this a good idea?
The main reason for lazy-loading is NOT to defer loading that will definitely occur. It is for deferring loading that may never be needed. (It's also good for forcing reloads when the data has changed, but that's not your issue here.)
Example: Let's say you have a bunch of data about a person, including a photo, which is stored in an external file. But the photo will only be displayed if the user goes to a subview, so why load the photo from its file until you know for sure that the subview is going to appear? Boom, use lazy loading.
By the time you KNOW you want to load a certain piece of data, it's unlikely to matter very much when exactly you load it.
When does it matter? Well, that's really a matter of optimization. There's a saying you may have run across; if you haven't, this is as good a time as any: "Premature optimization is the root of all (programming) evil."
So ask yourself two questions:
Will the piece of data definitely be needed? If NO, proceed with the lazy-loading technique. If YES, go to question 2.
Does it MATTER when I load the data? [An example would be, it's huge and I don't want to load it until I've UNLOADED something else to make room for it] If NO, put it any place that works. If YES... Come back and ask us again, and provide more details.
...I suspect this doesn't answer your original question, but it sounds like you may be asking the wrong question in the first place. Apologies if I'm mistaken.