State Preservation and Restoration

Even if your app supports background execution, it cannot run forever. At some point, the system might need to terminate your app to free up memory for the current foreground app. However, the user should never have to care if an app is already running or was terminated. From the user’s perspective, quitting an app should just seem like a temporary interruption. When the user returns to an app, that app should always return the user to the last point of use, so that the user can continue with whatever task was in progress. This behavior provides a better experience for the user and with the state restoration support built in to UIKit is relatively easy to achieve.

The state preservation system in UIKit provides a simple but flexible infrastructure for preserving and restoring the state of your app’s view controllers and views. The job of the infrastructure is to drive the preservation and restoration processes at the appropriate times. To do that, UIKit needs help from your app. Only you understand the content of your app, and so only you can write the code needed to save and restore that content. And when you update your app’s UI, only you know how to map older preserved content to the newer objects in your interface.

There are three places where you have to think about state preservation in your app:

UIKit allows you to choose which parts of your user interface you want to preserve. And if you already have custom code for handling state preservation, you can continue to use that code and migrate portions to the UIKit state preservation system as needed.

The Preservation and Restoration Process

State preservation and restoration is an opt-in feature and requires help from your app to work. Your app essentially provides UIKit with a list of objects and lets UIKit handle the tedious aspects of preserving and restoring those objects at appropriate times. Because UIKit handles so much of the process, it helps to understand what it does behind the scenes so that you know how your custom code fits into the overall scheme.

When thinking about state preservation and restoration, it helps to separate the two processes first. State preservation occurs when your app moves to the background. At that time, UIKit queries your app’s views and view controllers to see which ones should be preserved and which ones should not. For each object that should be preserved, UIKit writes preservation-related data to an on-disk file. The next time your app launches from scratch, UIKit looks for that file and, if it is present, uses it to try and restore your app’s state. During the restoration process, UIKit uses the preserved data to reconstitute your interface. The creation of actual objects is handled by your code. Because your app might load objects from a storyboard file automatically, only your code knows which objects need to be created and which might already exist and can simply be returned. After those objects are created, UIKit uses the on-disk data to restore the objects to their previous state.

During the preservation and restoration process, your app has a handful of responsibilities.

Of your app’s responsibilities, the most significant are telling UIKit which objects to preserve and providing those objects during subsequent launches. Those two behaviors are where you should spend most of your time when designing your app’s preservation and restoration code. They are also where you have the most control over the actual process. To understand why that is the case, it helps to look at an example.

Figure 4-1 shows the view controller hierarchy of a tab bar interface after the user has interacted with several of the tabs. As you can see, some of the view controllers are loaded automatically as part of the app’s main storyboard file but some of the view controllers were presented or pushed onto the view controllers in different tabs. Without state restoration, only the view controllers from the main storyboard file would be restored during subsequent launches. By adding support for state restoration to your app, you can preserve all of the view controllers.

Figure 4-1  A sample view controller hierarchy

UIKit preserves only those objects that have a restoration identifier. A restoration identifier is a string that identifies the view or view controller to UIKit and your app. The value of this string is significant only to your code but the presence of this string tells UIKit that it needs to preserve the tagged object. During the preservation process, UIKit walks your app’s view controller hierarchy and preserves all objects that have a restoration identifier. If a view controller does not have a restoration identifier, that view controller and all of its views and child view controllers are not preserved. Figure 4-2 shows an updated version of the previous view hierarchy, now with restoration identifies applied to most (but not all) of the view controllers.

Figure 4-2  Adding restoration identifies to view controllers

Depending on your app, it might or might not make sense to preserve every view controller. If a view controller presents transitory information, you might not want to return to that same point on restore, opting instead to return the user to a more stable point in your interface.

For each view controller you choose to preserve, you also need to decide on how you want to restore it later. UIKit offers two ways to recreate objects. You can let your app delegate recreate it or you can assign a restoration class to the view controller and let that class recreate it. A restoration class implements the UIViewControllerRestoration protocol and is responsible for finding or creating a designated object at restore time. Here are some tips for when to use each one:

During the preservation process, UIKit identifies the objects to save and writes each affected object’s state to disk. Each view controller object is given a chance to write out any data it wants to save. For example, a tab view controller saves the identity of the selected tab. UIKit also saves information such as the view controller’s restoration class to disk. And if any of the view controller’s views has a restoration identifier, UIKit asks them to save their state information too.

The next time the app is launched, UIKit loads the app’s main storyboard or nib file as usual, calls the app delegate’s application:willFinishLaunchingWithOptions: method, and then tries to restore the app’s previous state. The first thing it does is ask your app to provide the set of view controller objects that match the ones that were preserved. If a given view controller had an assigned restoration class, that class is asked to provide the object; otherwise, the app delegate is asked to provide it.

Flow of the Preservation Process

Figure 4-3 shows the high-level events that happen during state preservation and shows how the objects of your app are affected. Before preservation even occurs, UIKit asks your app delegate if it should occur by calling the application:shouldSaveApplicationState: method. If that method returns YES, UIKit begins gathering and encoding your app’s views and view controllers. When it is finished, it writes the encoded data to disk.

Figure 4-3  High-level flow interface preservation

The next time your app launches, the system automatically looks for a preserved state file, and if present, uses it to restore your interface. Because this state information is only relevant between the previous and current launch cycles of your app, the file is typically discarded after your app finishes launching. The file is also discarded any time there is an error restoring your app. For example, if your app crashes during the restoration process, the system automatically throws away the state information during the next launch cycle to avoid another crash.

Flow of the Restoration Process

Figure 4-4 shows the high-level events that happen during state restoration and shows how the objects of your app are affected. After the standard initialization and UI loading is complete, UIKit asks your app delegate if state restoration should occur at all by calling the application:shouldRestoreApplicationState: method. This is your app delegate’s opportunity to examine the preserved data and determine if state restoration is possible. If it is, UIKit uses the app delegate and restoration classes to obtain references to your app’s view controllers. Each object is then provided with the data it needs to restore itself to its previous state.

Figure 4-4  High-level flow for restoring your user interface

Although UIKit helps restore the individual view controllers, it does not automatically restore the relationships between those view controllers. Instead, each view controller is responsible for encoding enough state information to return itself to its previous state. For example, a navigation controller encodes information about the order of the view controllers on its navigation stack. It then uses this information later to return those view controllers to their previous positions on the stack. Other view controllers that have embedded child view controllers are similarly responsible for encoding any information they need to restore their children later.

Because you are responsible for recreating your app’s view controllers, you have some flexibility to change your interface during the restoration process. For example, you could reorder the tabs in a tab bar controller and still use the preserved data to return each tab to its previous state. Of course, if you make dramatic changes to your view controller hierarchy, such as during an app update, you might not be able to use the preserved data.

What Happens When You Exclude Groups of View Controllers?

When the restoration identifier of a view controller is nil, that view controller and any child view controllers it manages are not preserved automatically. For example, in Figure 4-6, because a navigation controller did not have a restoration identifier, it and all of its child view controllers and views are omitted from the preserved data.

Figure 4-5  Excluding view controllers from the automatic preservation process

Even if you decide not to preserve view controllers, that does not mean all of those view controllers disappear from the view hierarchy altogether. At launch time, your app might still create the view controllers as part of its default setup. For example, if any view controllers are loaded automatically from your app’s storyboard file, they would still appear, albeit in their default configuration, as shown in Figure 4-6.

Figure 4-6  Loading the default set of view controllers

Something else to realize is that even if a view controller is not preserved automatically, you can still encode a reference to that view controller and preserve it manually. In Figure 4-5, the three child view controllers of the first navigation controller have restoration identifiers, even though there parent navigation controller does not. If your app delegate (or any preserved object) encodes a reference to those view controllers, their state is preserved. Even though their order in the navigation controller is not saved, you could still use those references to recreate the view controllers and install them in the navigation controller during subsequent launch cycles.

Checklist for Implementing State Preservation and Restoration

Supporting state preservation and restoration requires modifying your app delegate and view controller objects to encode and decode the state information. If your app has any custom views that also have preservable state information, you need to modify those objects too.

When adding state preservation and restoration to your code, use the following list to remind you of the code you need to write.

Enabling State Preservation and Restoration in Your App

State preservation and restoration is not an automatic feature and apps must opt-in to use it. Apps indicate their support for the feature by implementing the following methods in their app delegate:

Normally, your implementations of these methods just return YES to indicate that state preservation and restoration can occur. However, apps that want to preserve and restore their state conditionally can return NO in situations where the operations should not occur. For example, after releasing an update to your app, you might want to return NO from your application:shouldRestoreApplicationState: method if your app is unable to usefully restore the state from a previous version.

Preserving the State of Your View Controllers

Preserving the state of your app’s view controllers should be your main goal. View controllers define the structure of your user interface. They manage the views needed to present that interface and they coordinate the getting and setting of the data that backs those views. To preserve the state of a single view controller, you must do the following:

Marking Your View Controllers for Preservation

UIKit preserves only those view controllers whose restorationIdentifier property contains a valid string object. For view controllers that you know you want to preserve, set the value of this property when you initialize the view controller object. If you load the view controller from a storyboard or nib file, you can set the restoration identifier there.

Choosing an appropriate value for restoration identifiers is important. During the restoration process, your code uses the restoration identifier to determine which view controller to retrieve or create. If every view controller object is based on a different class, you can use the class name for the restoration identifier. However, if your view controller hierarchy contains multiple instances of the same class, you might need to choose different names based on each view usage.

When it asks you to provide a view controller, UIKit provides you with the restoration path of the view controller object. A restoration path is the sequence of restoration identifiers starting at the root view controller and walking down the view controller hierarchy to the current object. For example, imagine you have a tab bar controller whose restoration identifier is TabBarControllerID, and the first tab contains a navigation controller whose identifier is NavControllerID and whose root view controller’s identifier is MyViewController. The full restoration path for the root view controller would be TabBarControllerID/NavControllerID/MyViewController.

The restoration path for every object must be unique. If a view controller has two child view controllers, each child must have a different restoration identifier. However, two view controllers with different parent objects may use the same restoration identifier because the rest of the restoration path provides the needed uniqueness. Some UIKit view controllers, such as navigation controllers, automatically disambiguate their child view controllers, allowing you to use the same restoration identifiers for each child. For more information about the behavior of a given view controller, see the corresponding class reference.

At restore time, you use the provided restoration path to determine which view controller to return to UIKit. For more information on how you use restoration identifiers and restoration paths to restore view controllers, see “Restoring Your View Controllers at Launch Time.”

Restoring Your View Controllers at Launch Time

During the restoration process, UIKit asks your app to create (or locate) the view controller objects that comprise your preserved user interface. UIKit adheres to the following process when trying to locate view controllers:

  1. If the view controller had a restoration class, UIKit asks that class to provide the view controller. UIKit calls the viewControllerWithRestorationIdentifierPath:coder: method of the associated restoration class to retrieve the view controller. If that method returns nil, it is assumed that the app does not want to recreate the view controller and UIKit stops looking for it.

  2. If the view controller did not have a restoration class, UIKit asks the app delegate to provide the view controller. UIKit calls the application:viewControllerWithRestorationIdentifierPath:coder: method of your app delegate to look for view controllers without a restoration class. If that method returns nil, UIKit tries to find the view controller implicitly.

  3. If a view controller with the correct restoration path already exists, UIKit uses that object. If your app creates view controllers at launch time (either programmatically or by loading them from a resource file) and assigns restoration identifiers to them, UIKit finds them implicitly through their restoration paths.

  4. If the view controller was originally loaded from a storyboard file, UIKit uses the saved storyboard information to locate and create it. UIKit saves information about a view controller’s storyboard inside the restoration archive. At restore time, it uses that information to locate the same storyboard file and instantiate the corresponding view controller if the view controller was not found by any other means.

It is worth noting that if you specify a restoration class for a view controller, UIKit does not try to find your view controller implicitly. If the viewControllerWithRestorationIdentifierPath:coder: method of your restoration class returns nil, UIKit stops trying to locate your view controller. This gives you control over whether you really want to create the view controller. If you do not specify a restoration class, UIKit does everything it can to find the view controller for you, creating it as necessary from your app’s storyboard files.

If you choose to use a restoration class, the implementation of your viewControllerWithRestorationIdentifierPath:coder: method should create a new instance of the class, perform some minimal initialization, and return the resulting object. Listing 4-1 shows an example of how you might use this method to load a view controller from a storyboard. Because the view controller was originally loaded from a storyboard, this method uses the UIStateRestorationViewControllerStoryboardKey key to get the storyboard from the archive. Note that this method does not try to configure the view controller’s data fields. That step occurs later when the view controller’s state is decoded.

Listing 4-1  Creating a new view controller during restoration

+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
                      coder:(NSCoder *)coder {
   MyViewController* vc;
   UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
   if (sb) {
      vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
      vc.restorationIdentifier = [identifierComponents lastObject];
      vc.restorationClass = [MyViewController class];
   }
    return vc;
}

Reassigning the restoration identifier and restoration class, as in the preceding example, is a good habit to adopt when creating new view controllers. The simplest way to restore the restoration identifier is to grab the last item in the identifierComponents array and assign it to your view controller.

For objects that were already loaded from your app’s main storyboard file at launch time, do not create a new instance of each object. Instead, implement the application:viewControllerWithRestorationIdentifierPath:coder: method of your app delegate and use it to return the appropriate objects or let UIKit find those objects implicitly.

Encoding and Decoding Your View Controller’s State

For each object slated for preservation, UIKit calls the object’s encodeRestorableStateWithCoder: method to give it a chance to save its state. During the decode process, a matching call to the decodeRestorableStateWithCoder: method is made to decode that state and apply it to the object. The implementation of these methods is optional, but recommended, for your view controllers. You can use them to save and restore the following types of information:

  • References to any data being displayed (not the data itself)

  • For a container view controller, references to its child view controllers

  • Information about the current selection

  • For view controllers with a user-configurable view, information about the current configuration of that view.

In your encode and decode methods, you can encode any values supported by the coder, including other objects. For all objects except views and view controllers, the object must adopt the NSCoding protocol and use the methods of that protocol to write its state. For views and view controllers, the coder does not use the methods of the NSCoding protocol to save the object’s state. Instead, the coder saves the restoration identifier of the object and adds it to the list of preservable objects, which results in that object’s encodeRestorableStateWithCoder: method being called.

The encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: methods of your view controllers should always call super at some point in their implementation. Calling super gives the parent class a chance to save and restore any additional information. Listing 4-2 shows a sample implementation of these methods that save a numerical value used to identify the specified view controller.

Listing 4-2  Encoding and decoding a view controller’s state.

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
   [super encodeRestorableStateWithCoder:coder];
 
   [coder encodeInt:self.number forKey:MyViewControllerNumber];
}
 
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
   [super decodeRestorableStateWithCoder:coder];
 
   self.number = [coder decodeIntForKey:MyViewControllerNumber];
}

Coder objects are not shared during the encode and decode process. Each object with preservable state receives its own coder that it can use to read or write data. The use of unique coders means that you do not have to worry about key namespace collisions among your own objects. However, you must still avoid using some special key names that UIKit provides. Specifically, each coder contains the UIApplicationStateRestorationBundleVersionKey and UIApplicationStateRestorationUserInterfaceIdiomKey keys, which provide information about the bundle version and current user interface idiom. Coders associated with view controllers may also contain the UIStateRestorationViewControllerStoryboardKey key, which identifies the storyboard from which that view controller originated.

For more information about implementing your encode and decode methods for your view controllers, see UIViewController Class Reference.

Preserving the State of Your Views

If a view has state information worth preserving, you can save that state with the rest of your app’s view controllers. Because they are usually configured by their owning view controller, most views do not need to save state information. The only time you need to save a view’s state is when the view itself can be altered by the user in a way that is independent of its data or the owning view controller. For example, scroll views save the current scroll position, which is information that is not interesting to the view controller but which does affect how the view presents itself.

To designate that a view’s state should be saved, you do the following:

As with view controllers, assigning a restoration identifier to a view tells the system that the view object has state that your app wants to save. The restoration identifier can also be used to locate the view later.

Like view controllers, views define methods for encoding and decoding their custom state. If you create a view with state worth saving, you can use these methods to read and write any relevant data.

UIKit Views with Preservable State

In order to save the state of any view, including both custom and standard system views, you must assign a restoration identifier to the view. Views without a restoration identifier are not added to the list of preservable objects by UIKit.

The following UIKit views have state information that can be preserved:

Other frameworks may also have views with preservable state. For information about whether a view saves state information and what state it saves, see the reference for the corresponding class.

Preserving the State of a Custom View

If you are implementing a custom view that has restorable state, implement the encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: methods and use them to encode and decode that state. Use those methods to save only the data that cannot be easily reconfigured by other means. For example, use these methods to save data that is modified by user interactions with the view. Do not use these methods to save the data being presented by the view or any data that the owning view controller can configure easily.

Listing 4-3 shows an example of how to preserve and restore the selection for a custom view that contains editable text. In the example, the range is accessible using the selectionRange and setSelectionRange: methods, which are custom methods the view uses to manage the selection. Encoding the data only requires writing it to the provided coder object. Restoring the data requires reading it and applying it to the view.

Listing 4-3  Preserving the selection of a custom text view

// Preserve the text selection
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
    [super encodeRestorableStateWithCoder:coder];
 
    NSRange range = [self selectionRange];
    [coder encodeInt:range.length forKey:kMyTextViewSelectionRangeLength];
    [coder encodeInt:range.location forKey:kMyTextViewSelectionRangeLocation];
}
 
// Restore the text selection.
- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
   [super decodeRestorableStateWithCoder:coder];
   if ([coder containsValueForKey:kMyTextViewSelectionRangeLength] &&
           [coder containsValueForKey:kMyTextViewSelectionRangeLocation]) {
      NSRange range;
      range.length = [coder decodeIntForKey:kMyTextViewSelectionRangeLength];
      range.location = [coder decodeIntForKey:kMyTextViewSelectionRangeLocation];
      if (range.length > 0)
         [self setSelectionRange:range];
   }
}

Implementing Preservation-Friendly Data Sources

Because the data displayed by a table or collection view can change, both classes save information about the current selection and visible cells only if their data source implements the UIDataSourceModelAssociation protocol. This protocol provides a way for a table or collection view to identify the content it contains without relying on the index path of that content. Thus, regardless of where the data source places an item during the next launch cycle, the view still has all the information it needs to locate that item.

In order to implement the UIDataSourceModelAssociation protocol successfully, your data source object must be able to identify items between subsequent launches of the app. This means that any identification scheme you devise must be invariant for a given piece of data. This is essential because the data source must be able to retrieve the same piece of data for the same identifier each time it is requested. Implementing the protocol itself is a matter of mapping from a data item to its unique ID and back again.

Apps that use Core Data can implement the protocol by taking advantage of object identifiers. Each object in a Core Data store has a unique object identifier that can be converted into a URI and used to locate the object later. If your app does not use Core Data, you need to devise your own form of unique identifiers if you want to support state preservation for your views.

Preserving Your App’s High-Level State

In addition to the data preserved by your app’s view controllers and views, UIKit provides hooks for you to save any miscellaneous data needed by your app. Specifically, the UIApplicationDelegate protocol includes the following methods for you to override:

If your app contains state that does not live in a view controller, but that needs to be preserved, you can use the preceding methods to save and restore it. The application:willEncodeRestorableStateWithCoder: method is called at the very beginning of the preservation process so that you can write out any high-level app state, such as the current version of your user interface. The application:didDecodeRestorableStateWithCoder: method is called at the end of the restoration state so that you can decode any data and perform any final cleanup that your app requires.

Mixing UIKit’s State Preservation with Your Own Custom Mechanisms

If your app already implements its own custom state preservation and restoration mechanism, you can continue to use that mechanism and migrate your code to use UIKit’s support over time. The design of UIKit’s preservation mechanism allows you to pick and choose which view controllers you want to preserve. Thus, you can designate that only portions of your interface should be restored by UIKit, leaving the rest to be handled by your app’s current process.

Figure 4-7 shows a sample view hierarchy containing a tab bar controller and the view controllers in its assorted tabs. In this sample, because the tab bar controller has a restoration identifier associated with it, UIKit saves the state of the tab bar controller and all other child view controllers that also have a restoration identifier. Your app’s custom code would then need to preserve the state of the remaining view controllers. During restoration, a similar process occurs. UIKit restores all of the view controllers that it preserved while your custom code restores the rest.

Figure 4-7  UIKit handles the root view controller

If you prefer to have your own code manage the root view controller of your app, the save and restore process differs slightly. Because UIKit would not automatically save any view controllers, you need to encode them manually in the application:willEncodeRestorableStateWithCoder: method of your app delegate. When you use the encodeObject:forKey: method of the coder to encode a view controller object, the coder uses the view controller’s encodeRestorableStateWithCoder: method to do the encoding. This process allows you to write arbitrary view controllers to the state preservation archive managed by UIKit.

When you decode archived view controllers during the next launch cycle, you must still be prepared to provide an instance of each view controller to UIKit. When you call the decodeObjectForKey: method to decode your view controller, UIKit calls the application:viewControllerWithRestorationIdentifierPath:coder: method of your app delegate to retrieve the view controller object first. Only after UIKit has the view controller object does it call the decodeRestorableStateWithCoder: method to return the view controller to its previous state. Your code can use the application:viewControllerWithRestorationIdentifierPath:coder: method to create the view controller and install it in your app’s view controller hierarchy.

Tips for Saving and Restoring State Information

As you add support for state preservation and restoration to your app, consider the following guidelines: