programa - ¿Cómo manejar mejor la restauración del estado de iOS CoreData+?
restaurar iphone 4 sin itunes (6)
Aprendí una manera muy limpia de configurar la pila de datos básicos de NSScreencast . Básicamente, inicias tu proyecto Xcode sin elegir la opción "Usar datos principales". Luego agrega una clase singleton que es su modelo de datos. Entonces, para obtener el MOC principal, haría [[DataModel sharedModel] mainContext]
. Me parece mucho más limpio que tirar todo en el delegado de la aplicación.
Nunca lo he usado de esta manera, pero supongo que en su caso también podría hacer esto en sus controladores de vista:
-(NSManagedObjectContext*)moc
{
if (_moc != nil) return _moc;
_moc = [[DataModel sharedModel] mainContext];
return _moc;
}
Estoy tratando de agregar iOS 6 State Restoration a una aplicación que estoy por terminar. Es una aplicación donde el modelo proviene principalmente de CoreData.
Como se recommended , estoy usando el enfoque "pasar el testigo" para mover contextos de objetos administrados entre los controladores de vista: creo el MOC en mi delegado de aplicación, lo paso al primer controlador de visualización, que lo pasa al segundo en prepare for legue :, que lo pasa al tercero en prepareForSegue :, etc.
Esto no parece muy bien con la Restauración del Estado. Lo único que se me ocurre hacer es recuperar el MOC de mi delegado de aplicación directamente en una implementación de viewControllerWithRestorationIdentifierPath: coder :. De hecho, parece que los desarrolladores de Apple hicieron algo similar al ver la sesión WWDC.
¿Es esta la mejor / única manera? ¿La Restauración del Estado efectivamente rompe Pass-The-Baton, al menos para los controladores de vista que se restauran?
Creo que la mejor manera de manejar esto sería codificar el MOC en:
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
y luego decodificar cuando se restaura mediante:
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
Esto debería retener el enfoque de pasar el bastón entre las restauraciones estatales.
Tenga en cuenta que cada VC que use un MOC debe implementar esto si va con este enfoque.
Para expandir ligeramente, utilice el + (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
método del + (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
para inicializar su VC, entonces el MOC debe decodificarse mediante el método mencionado anteriormente y usted debe estar todo listo.
This esperar que esto brinde suficiente información para que puedas avanzar en la codificación y decodificación de cualquier información que quieras recuperar al restaurar.
Es posible modificar la plantilla de datos principales del maestro-detalle para que funcione con la restauración y mantener el diseño ''pasar el testigo''. Simplemente cambie didFinishLaunchingWithOptions
a willFinishLaunching
para que establezca el contexto en MasterViewController
antes de que comience el proceso de restauración de estado.
Solucione una falla en la plantilla cambiando (MasterViewController *)masterNavigationController.topViewController
para ver los viewControllers[0]
, de modo que encuentre el controlador de la vista maestra cada vez, es decir, durante una restauración, el controlador de navegación del controlador de detalle podría ser la parte superior.
En el guión gráfico, establezca ID de restauración en todos los controladores de división, controladores de navegación, vistas de tabla y celdas de tabla.
Devuelve true desde los métodos de restauración de delegados de la aplicación.
Edite la interfaz MasterViewController
para implementar UIDataSourceModelAssociation que es necesaria para restaurar UITableView
, y añada estos métodos a la implementación:
- (nullable NSString *) modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx inView:(UIView *)view{
Event *event = [self.fetchedResultsController objectAtIndexPath:idx];
return event.objectID.URIRepresentation.absoluteString;
}
- (nullable NSIndexPath *) indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view{
NSURL *url = [NSURL URLWithString:identifier];
NSManagedObjectID *objectID = [self.managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:url];
Event *event = [self.managedObjectContext objectWithID:objectID]; // needs error checking
return [self.fetchedResultsController indexPathForObject:event];
}
También en MasterViewController
pasa el managedObjectContext al detailViewController.
- (void)viewDidLoad{
[super viewDidLoad];
...
DetailViewController *detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
detailViewController.managedObjectContext = self.managedObjectContext;
self.detailViewController = detailViewController;
...
Asegúrese de estar utilizando el nuevo dequeueReusableCellWithIdentifier:forIndexPath:
porque eso es lo que le da una celda que ya está seleccionada.
En DetailViewController
implemente los métodos de restauración de codificación:
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder{
[super encodeRestorableStateWithCoder:coder];
NSManagedObjectID *objectID = self.detailItem.objectID;
[coder encodeObject:objectID.URIRepresentation forKey:@"detailItem"];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
NSURL *objectURI = [coder decodeObjectForKey:@"detailItem"];
NSManagedObjectID *objectID = [self.managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:objectURI];
NSManagedObject *detailItem = [self.managedObjectContext objectWithID:objectID];
self.detailItem = detailItem;
}
Implemente también un getter para managedObjectContext:
- (NSManagedObjectContext *)managedObjectContext{
if(!_managedObjectContext){
_managedObjectContext = self.detailItem.managedObjectContext;
}
return _managedObjectContext;
}
Mi solución a esto ha sido hacer que los controladores de vista utilicen de forma predeterminada un MOC global compartido. Este contexto será el previsto en la mayoría de los casos, y si necesita pasar cualquier otro MOC, es perfectamente posible hacerlo.
Este enfoque confirma el enfoque de "pasar el testigo" de Apple, además de ser conveniente y compatible con la restauración del estado.
No he hecho mucho con la restauración del estado, pero pensaría lo siguiente:
¿La aplicación delegada se despierta primero? ¿Hay alguna oportunidad para que el delegado de la aplicación recorra los controladores de visualización?
¿Puede el controlador de visualización pausar mientras espera que AppDelegate le proporcione el contexto?
Parece que Restauración de estado podría ser un caso especial, pero exploraría la opción de hacer que los controladores de visualización sean lo suficientemente inteligentes como para esperar a que aparezca el MOC antes de solicitar datos. Tal vez incluso tener un estado de recuperación en los controladores de vista donde retroceden a un lugar donde el controlador de vista puede esperar el contexto.
Para familiarizarse con la restauración del estado, recomiendo encarecidamente la sesión de la WWDC 2013 Novedades en la restauración del estado . Mientras que la restauración del estado se introdujo un año antes en iOS 6, iOS 7 trajo algunos cambios notables.
Pasándolo adelante
Usando el enfoque "pasar el bastón", en algún punto se crea un NSPersistentStoreCoordinator
raíz y se adjunta un NSPersistentStoreCoordinator
. El contexto pasa a un controlador de vista y los controladores de vista secundarios posteriores pasan a su vez ese contexto raíz o un contexto secundario.
Por ejemplo, cuando el usuario inicia la aplicación, se crea el NSManagedObjectContext
raíz y se pasa al controlador de vista raíz, que administra un NSFetchedResultsController
. Cuando el usuario selecciona un elemento en el controlador de vista, se crea un nuevo controlador de vista detallada y se NSManagedObjectContext
una instancia de NSManagedObjectContext
.
Estado de ahorro y restauración
La restauración del estado cambia esto en formas que son significativas para las aplicaciones que usan datos centrales en los controladores de vista. Si el usuario está en el controlador de vista detallada y envía la aplicación al fondo, el sistema crea un archivo de restauración con información útil para reconstruir el estado visible cuando se fueron. Se escribe la información sobre toda la cadena de controladores de vista, y cuando se relanza la aplicación, se usa para reconstruir el estado.
Cuando esto sucede, no utiliza ningún inicializador personalizado, segues, etc. El protocolo UIStateRestoring
define los métodos utilizados para el estado de codificación y descodificación que permiten cierto grado de personalización. Los objetos que se ajustan a NSCoding
se pueden almacenar en archivos de restauración y en iOS 7 la restauración de estado se amplió para modelar objetos y fuentes de datos.
La restauración del estado está destinada a almacenar solo la información que se requiere para reconstruir el estado visible de la aplicación. Para una aplicación de Datos básicos, esto significa almacenar la información necesaria para ubicar el objeto en el almacén persistente correcto.
En la superficie, esto parece simple. En el caso de un controlador de vista que gestiona un NSFetchedResultsController
esto puede significar almacenar el predicado y clasificar descriptores. Para un controlador de vista de detalle que muestra o edita un único objeto gestionado, la representación de URI del objeto gestionado se agregará al archivo de restauración de estado:
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
NSManagedObjectID *objectID = [[self managedObject] objectID];
[coder encodeObject:[objectID URIRepresentation] forKey:kManagedObjectKeyPath];
[super encodeRestorableStateWithCoder:coder];
}
Cuando se restablece el estado, se llama al método -decodeRestorableStateWithCoder:
para restaurar el objeto de la información archivada:
- Decodifica el URI del archivo de restauración.
- Obtener un ID de objeto administrado para el URI del coordinador de tienda persistente
- Obtenga una instancia de objeto gestionado desde el contexto del objeto gestionado para esa ID de objeto gestionado
Por ejemplo:
- (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];
}];
}];
}
Y aquí es donde las cosas se vuelven más complicadas. En el punto en el ciclo de vida de la aplicación donde se llama -decodeRestorableStateWithCoder:
el controlador de vista necesitará el NSManagedObjectContext
correcto .
Con el enfoque "pasar el testigo", se creó una instancia del controlador de vista como resultado de la interacción del usuario, y se transfirió un contexto de objeto gestionado. Ese contexto de objeto gestionado se conectó a un contexto padre o coordinador de tienda persistente.
Durante la restauración del estado eso no sucede. Si nos fijamos en las ilustraciones de lo que sucede durante "pasar el bastón" frente a la restauración del estado, pueden parecer muy similares, y lo son. Durante la restauración del estado, los datos se transfieren: la instancia de NSCoder
que representa una interfaz para el archivo de restauración.
Lamentablemente, la información de NSManagedObjectContext
que necesitamos no se puede almacenar como parte del archivo de restauración. NSManagedObjectContext
cumple con NSCoding
, pero las partes importantes no lo hacen. NSPersistentStoreCoordinator
no lo hace, por lo que no se mantendrá. Curiosamente, la propiedad parentContext
de un NSManagedObjectContext
tampoco lo hará (recomendaría encarecidamente archivar un radar sobre esto). Almacenar las URL de instancias de NSPersistentStore
específicas y recrear un NSPersistentStoreCoordinator
en cada controlador de vista puede parecer una opción atractiva, pero el resultado será un coordinador diferente para cada controlador de vista, lo que puede conducir rápidamente a un desastre.
Entonces, aunque la restauración de estado puede proporcionar la información necesaria para ubicar entidades en un NSManagedObjectContext
, no puede proporcionar directamente lo que se necesita para recrear el contexto en sí.
Entonces, ¿qué sigue?
En última instancia, lo que se necesita en un controlador de vista es -decodeRestorableStateWithCoder:
es una instancia de NSManagedObjectContext
que tiene el mismo origen que cuando se codificó el estado. Debe tener la misma estructura de contextos ancestrales y tiendas persistentes.
La restauración del estado comienza en UIApplicationDelegate, donde se invocan varios métodos de delegado como parte del proceso de restauración ( -application:willFinishLaunchingWithOptions:
-application:shouldRestoreApplicationState:
-didDecodeRestorableStateWithCoder:
-application:viewControllerWithRestorationIdentifierPath:coder:
. Cada una de estas es una oportunidad para personalizar el proceso de restauración desde el principio y pasar información a lo largo de la misma, como adjuntar una instancia de NSManagedObjectContext
como una referencia de objeto asociado al NSCoder
utilizado para la restauración.
Si el objeto delegado de la aplicación es responsable de crear el contexto raíz, ese objeto podría ser empujado hacia abajo a lo largo de la cadena de controlador de vista una vez que el proceso de inicio esté completo (con restauración de estado o sin esta). Cada controlador de vista pasará la instancia de NSManagedObjectContext
apropiada a sus controladores de vista secundarios:
@implementation UIViewController (CoreData)
- (void) setManagedObjectContext:(NSManagedObjectContext *)context {
[[self childViewControllers] makeObjectsPerformSelector:_cmd withObject:context];
}
@end
Y cada controlador de vista que proporcionó su propia implementación crearía un contexto propio propio. Esto tiene otras ventajas: cualquier enfoque que haga reaccionar a los usuarios de un contexto de objeto gestionado hace que sea más fácil crear el contexto de forma asíncrona. Crear un contexto en sí es rápido y ligero, pero agregar las tiendas persistentes al contexto raíz es potencialmente muy costoso y no se debe permitir que se ejecute en la cola principal. Muchas aplicaciones hacen esto en la cola principal en un método de delegado de aplicación y terminan siendo destruidas por el sistema operativo cuando abrir los archivos de la tienda lleva demasiado tiempo o se requiere una migración. Agregar el almacenamiento persistente en otro hilo y luego enviar el contexto a los objetos que lo usan cuando está listo puede ayudar a prevenir este tipo de problemas.
Otro enfoque puede ser aprovechar la cadena de respuesta en el controlador de vista. Durante la restauración del estado, el controlador de vista podría recorrer la cadena de respuesta para encontrar el siguiente NSManagedObjectContext
en la cadena, crear un contexto secundario y usar eso. Implementar esto usando un protocolo informal es simple y da como resultado una solución que es flexible y adaptable.
La implementación predeterminada del protocolo informal caminaría más arriba en la cadena de respuesta:
@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
Y cualquier objeto en la cadena de respuesta puede implementar -managedObjectContext
de -managedObjectContext
para proporcionar una implementación alternativa. Esto incluye al delegado de la aplicación, que sí participa en la cadena de respuesta. Usando el protocolo informal anterior, si un controlador view o view llama a -managedObjectContext
la implementación predeterminada llegaría hasta el delegado de la aplicación para devolver un resultado a menos que algún otro objeto a lo largo del proceso proporcione un resultado no nulo.
También tiene la opción de utilizar fábricas de clase de restauración con restauración de estado para reconstruir la cadena de contextos de objetos gestionados durante la restauración.
Estas soluciones no son apropiadas para cada aplicación o situación, solo usted puede decidir qué funcionará para usted.