Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to best handle CoreData + iOS State Restoration?

Tags:

I'm trying to add iOS 6 State Restoration to an app that I'm just about finished with. It's an app where the model mostly comes from CoreData.

As recommended, I'm using the "pass the baton" approach to moving managed object contexts between View Controllers - I create the MOC in my App Delegate, pass it to the first View Controller, which passes it to the second in prepareForSegue:, which passes it to the third in prepareForSegue:, etc.

This doesn't seem to jive very well with State Restoration. The only thing I can think of to do is to retrieve the MOC from my App Delegate directly in an implementation of viewControllerWithRestorationIdentifierPath:coder:. In fact, it appears that the Apple developers did something similar when watching the WWDC session.

Is this the best/only way? Does State Restoration effectively break Pass-The-Baton, at least for view controllers that are restored?

like image 210
bpapa Avatar asked Jul 31 '13 22:07

bpapa


People also ask

How can we restore app state and its data in iOS?

This ensures that our controllers are identified by UIKit to be restored. Open the AppDelegate. swift of our application. You would find that we have enabled restoration by returning true to two of the methods i.e shouldSaveApplicationState and shouldRestoreApplicationState.

What is state restoration in Swift?

Instead, they expect your app to be in the same state as when they left it. State preservation and restoration ensures that your app returns to its previous state when it launches again. At appropriate times, UIKit preserves the state of your app's views and view controllers to an encrypted file on disk.

What is state restoration?

State restoration is an often-overlooked feature in iOS that lets a user return to their app in the exact state in which they left it – regardless of what's happened behind the scenes. At some point, the operating system may need to remove your app from memory; this could significantly interrupt your user's workflow.


1 Answers

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.

Passing the Managed Object Context Baton

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.

State Restoration

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:

  1. Decode the URI from the restoration archive.
  2. Get a managed object ID for the URI from the persistent store coordinator
  3. 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.

like image 89
quellish Avatar answered Oct 08 '22 23:10

quellish