To become familiar with state restoration I highly recommend the WWDC 2013 session What's New in State Restoration. While state restoration was introduced a year earlier in iOS 6, iOS 7 brought some notable changes.
Passing it forward
Using the "pass the baton" approach, at some point a root NSManagedObjectContext
is created and an NSPersistentStoreCoordinator
is attached. The context is passed to a view controller and subsequent child view controllers are in turn passed that root context or a child context.
For example, when the user launches the application the root NSManagedObjectContext
is created and passed in to the root view controller, which manages an NSFetchedResultsController
. When the user selects an item in the view controller a new detail view controller is created and an NSManagedObjectContext
instance is passed in.
Saving and Restoring State
State restoration changes this in ways that are significant to applications using Core Data in view controllers. If the user is on the detail view controller and sends the application to the background the system creates a restoration archive with information useful for reconstructing the state visible when they left. Information about the entire chain of view controllers is written out, and when the application is relaunched this is used to reconstruct the state.
When this happens it does not use any custom initializers, segues, etc. The UIStateRestoring
protocol defines methods used for encoding and decoding state which allow for some degree of customization. Objects that conform to NSCoding
can be stored in restoration archives and in iOS 7 state restoration was extended to model objects and data sources.
State restoration is intended to store only the information that is required to reconstruct the visible state of the application. For a Core Data application this means storing the information needed to locate the object in the correct persistent store.
On the surface, this seems simple. In the case of a view controller managing an NSFetchedResultsController
this may mean storing the predicate and sort descriptors. For a detail view controller that displays or edits a single managed object the URI representation of the managed object would be added to the state restoration archive:
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
NSManagedObjectID *objectID = [[self managedObject] objectID];
[coder encodeObject:[objectID URIRepresentation] forKey:kManagedObjectKeyPath];
[super encodeRestorableStateWithCoder:coder];
}
When the state is restored the UIStateRestoring method -decodeRestorableStateWithCoder:
is called to restore the object from the archived information:
- Decode the URI from the restoration archive.
- Get a managed object ID for the URI from the persistent store coordinator
- Get a managed object instance from the managed object context for that managed object ID
For example:
- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
NSURL *objectURI = nil;
NSManagedObjectID *objectID = nil;
NSPersistentStoreCoordinator *coordinator = [[self managedObjectContext] persistentStoreCoordinator];
objectURI = [coder decodeObjectForKey:kManagedObjectKeyPath];
objectID = [coordinator managedObjectIDForURIRepresentation:objectURI];
[[self managedObjectContext] performBlock:^{
NSManagedObject *object = [self managedObjectContext] objectWithID:objectID];
[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self setManagedObject:object];
}];
}];
}
And this is where things become more complicated. At the point in the application life cycle where -decodeRestorableStateWithCoder:
is called the view controller will need the correct NSManagedObjectContext
.
Pass the Baton vs. State Restoration: FIGHT!
With the "pass the baton" approach the view controller was instantiated as a result of user interaction, and a managed object context was passed in. That managed object context was connected to a parent context or persistent store coordinator.
During state restoration that does not happen. If you look at the illustrations of what happens during "pass the baton" vs. state restoration they may look very similar - and they are. During state restoration data is passed along - the NSCoder
instance that represents an interface to the restoration archive.
Unfortunately the NSManagedObjectContext
information we require can't be stored as part of the restoration archive. NSManagedObjectContext
does conform to NSCoding
, however the important parts do not. NSPersistentStoreCoordinator
does not, so it will not be persisted. Curiously, the parentContext
property of an NSManagedObjectContext
also will not (I would strongly suggest filing a radar on this).
Storing the URLs of specific NSPersistentStore
instances and recreating an NSPersistentStoreCoordinator
in each view controller may seem like an attractive option but the result will be a different coordinator for each view controller - which can quickly lead to disaster.
So while state restoration can provide the information needed to locate entities in an NSManagedObjectContext
, it can't directly provide what is needed to recreate the context itself.
So what next?
Ultimately what is needed in a view controller's -decodeRestorableStateWithCoder:
is an instance of NSManagedObjectContext
that has the same parentage that it did when state was encoded. It should have the same structure of ancestor contexts and persistent stores.
State restoration begins in the UIApplicationDelegate, where several delegate methods are invoked as part of the restoration process (-application:willFinishLaunchingWithOptions:
, -application:shouldRestoreApplicationState:
, -didDecodeRestorableStateWithCoder:
, -application:viewControllerWithRestorationIdentifierPath:coder:
). Each one of these is an opportunity to customize the restoration process from the beginning and pass information along - such as attaching an NSManagedObjectContext
instance as an associated object reference to the NSCoder
used for restoration.
If the application delegate object is responsible for creating the root context that object could be pushed down throughout the view controller chain once the launch process is complete (with or without state restoration). Each view controller would pass the appropriate NSManagedObjectContext
instance to it's child view controllers:
@implementation UIViewController (CoreData)
- (void) setManagedObjectContext:(NSManagedObjectContext *)context {
[[self childViewControllers] makeObjectsPerformSelector:_cmd withObject:context];
}
@end
And each view controller that provided it's own implementation would create a child context of it's own. This has other advantages - any approach that has the users of a managed object context react to it changing makes it easier to create the context asynchronously. Creating a context itself is fast and lightweight, but adding the persistent stores to the root context is potentially very expensive and should not be allowed to run on the main queue. Many applications do this on the main queue in an application delegate method and end up being killed by the OS when opening the files of the store takes too long or a migration is required. Adding the persistent store on another thread and then sending the context to the objects that use it when it's ready can help prevent these kinds of problems.
Another approach may be to leverage the responder chain in the view controller. During state restoration the view controller could walk the responder chain to find the next NSManagedObjectContext
up the chain, create a child context, and use that. Implementing this using an informal protocol is simple, and results in a solution that is flexible and adaptable.
The default implementation of the informal protocol would walk further up the responder chain:
@implementation UIResponder (CoreData)
- (NSManagedObjectContext *) managedObjectContext {
NSManagedObjectContext *result = nil;
if ([self nextResponder] != nil){
if ([[self nextResponder] respondsToSelector:@selector(managedObjectContext)]){
result = [[self nextResponder] managedObjectContext];
}
}
return result;
}
@end
And any object in the responder chain can implement -managedObjectContext
to provide an alternate implementation. This includes the application delegate, which does participate in the responder chain. Using the informal protocol above, if a view or view controller calls -managedObjectContext
the default implementation would go all the way to the application delegate to return a result unless some other object along the way provided a non-nil result.
You also have the option of using restoration class factories with state restoration to reconstruct the chain of managed object contexts during restoration.
These solutions are not appropriate for every application or situation, only you can decide what will work for you.