Pretty much since the beginning of this project I’ve been aware of a bug in the iPhone app I am finishing. About a week in I figured out how to reproduce it. If you ran the app, then hit the home button to dismiss it, then tapped the program icon to bring it back and tried to switch tabs it would crash. If running in the debugger it would pop an EXC_BAD_ACCESS exception on a reference to a UIImage associated with the UITabBarController. I assumed it was just a mistake somewhere. After all, you have to – gulp – manage memory through manual reference counting (Hello, AddRef, never thought I’d see you again. How’s your buddy Release?), so the chance that we were doing one too many releases or one too few retains was not zero. I’d finish the feature set and then hunt it down.
When I finally got around to the hunt-it-down part things weren’t so obvious. As far as I could tell we were doing all the right things to the resources assigned to the tab bar controller. The fact that it only happened after dismissing the application with the home button and then bringing it back had to be meaningful. In fact I had been wondering for some time just what happened when the user hit the home button on an iPhod running iOS 4.x. It was obvious from application behavior that the app wasn’t exiting and restarting. At least, not every time. I started reading up on how iOS manages the application lifecycle, and some lights began going on. Apparently the home button didn’t cause an app to exit, at least not always. If the app didn’t exit it went into a background limbo state, and in that state the operating system might do things. It might, for example, “release unused views.” Oh, really? Lots of lights popped on all at once.
If you were used to writing applications for iOS 3.x (and the guys who started this app were), then you had birth and death to think about, just like any program. When your app started you got a call to application:didFinishLaunching in the app delegate, and when the app exited you got a call to applicationWillTerminate. Typically you allocated your resources at startup, deallocated them in the app delegate’s dealloc method, and life was simple. Enter iOS 4 and “fast task switching.” This facility is a component of Apple’s answer to multi-tasking in iOS 4, the entirety of which includes actual background processing, something I’m not going into now. The piece of it that is relevant here is the switching part. Task switching is what takes place when the user taps the home button, or uses something like Springboard.
If your app is running in the foreground, and the user switches tasks (or a phone call arrives, or anything else happens that would force the app to give up control of the screen) what happens depends on whether the app is compliant with fast task switching. By default all apps built against the 4.0 SDK are. If you want your app to exhibit the old behavior then you have to say so by putting the UIApplicationExitsOnSuspend key in info.plist and setting the value to true. If you do, then the app will simply exit, and applicationWillTerminate gets called as it used to.
If you don’t add that key to info.plist, then the behavior is entirely different. Oliver Drobnik has a great flowchart of the events and accompanying explanation on his Dr. Touch blog. Basically it goes like this: the app starts up fresh and application:didFinishLaunchingWithOptions is called (the replacement for application:didFinishLaunching), followed by applicationDidBecomeActive; the app is now running in foreground and gets switched; applicationWillResignActive is called, followed by applicationDidEnterBackground. After that last call the application is suspended. Unless it is actually doing background processing as alluded to above, nothing is happening, but the image is still in memory. In this stage the OS may, as mentioned, prune the app’s memory usage by releasing invisible graphics objects, and ultimately it may also dump the app from memory if it needs to. If that happens applicationWillTerminate will not be called. Something to keep in mind.
Now what happens going the other way? If the app is suspended in ram and the user taps its icon, then it first gets a call to applicationWillEnterForeground, followed by applicationDidBecomeActive. In order to avoid the problems we were having you need to at least tear down any invisible/unused views as the app is heading for the background, and then restore these when the app is summoned to the front again, or as needed. So, does applicationWillTerminate ever get called? The answer is pretty much “no.” If you’re building against the 4.0 SDK that method is essentially deprecated. Except… well comments on the Dr. Touch blog indicate that maybe it still does get called if your app is running on earlier devices such as the iPhone 3. I don’ t have one of those to test on, so I’ve decided they don’t exist and my app will never run on them. Solved!
By the way, if you want to see which apps are currently running/suspended on your iOS 4 device double-tap the Home button. The UI will slide up as it does for the Utility app, and a side-scrollable list of app icons will be displayed. When I did this for the first time I was surprised at how many apps were still resident in memory. There is a red badge on each icon that will let you kill the app, and as with a preemptive strike from the OS the app’s applicationWillTerminate method is not called when a user does this. In general I would ignore this list and let the OS manage the app pool, but as a developer it’s nice to know you can force an app out of ram for a restart if you need to.