NSUserDefaults poco confiable en iOS 8

Tengo una aplicación que utiliza [NSUserDefaults standardUserDefaults] para almacenar información de session. En general, esta información se verifica en el inicio de la aplicación y se actualiza al salir de la aplicación. Descubrí que parece funcionar de forma poco fiable en iOS 8.

Actualmente estoy probando en un iPad 2, aunque puedo probar en otros dispositivos si es necesario.

Algunas veces, los datos escritos antes de la salida no persistirán en el inicio de la aplicación. Igualmente, las keys eliminadas antes de salir a veces parecen existir después del lanzamiento.

He escrito el siguiente ejemplo, para tratar de ilustrar el problema:

- (void)viewDidLoad { [super viewDidLoad]; NSData *_dataArchive = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSLog(@"Value at launch - %@", _dataArchive); NSString *testString = @"TESTSTRING"; [[NSUserDefaults standardUserDefaults] setObject:testString forKey:@"Session"]; [[NSUserDefaults standardUserDefaults] synchronize]; _dataArchive = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSLog(@"Value after adding data - %@", _dataArchive); [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Session"]; [[NSUserDefaults standardUserDefaults] synchronize]; _dataArchive = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSLog(@"Value before exit - %@", _dataArchive); exit(0); } 

Ejecutando el código anterior, yo (por lo general) obtengo el resultado a continuación (que es lo que espero):

 Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - (null) 

Si luego comento las líneas que eliminan la key:

 //[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Session"]; //[[NSUserDefaults standardUserDefaults] synchronize]; 

Y ejecuta la aplicación tres veces, espero ver:

 Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING Value at launch - TESTSTRING Value after adding data - TESTSTRING Value before exit - TESTSTRING Value at launch - TESTSTRING Value after adding data - TESTSTRING Value before exit - TESTSTRING 

Pero el resultado que realmente veo es:

 Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING 

Por ejemplo, parece que no está actualizando el valor al salir de la aplicación.

EDITAR : He probado el mismo código en un iPad 2 con iOS 7.1.2; y parece funcionar correctamente cada vez.

TLDR: en iOS 8, ¿[NSUserDefaults standardUserDefaults] funciona de manera poco fiable? ¿Y si es así hay una solución / solución?

iOS 8 introdujo una serie de cambios de comportamiento en NSUserDefaults . Si bien la API NSUserDefaults ha cambiado poco, el comportamiento ha cambiado de forms que pueden ser relevantes para su aplicación. Por ejemplo, usar -synchronize está -synchronize (y siempre lo ha sido). Los cambios de adición a otras partes de Foundation y CoreFoundation, como la coordinación de files y los cambios relacionados con los contenedores compartidos, pueden afectar su aplicación y el uso de NSUserDefaults .

Escribir en NSUserDefaults en particular ha cambiado debido a esto. La escritura lleva más time y es posible que haya otros processs que compitan por el acceso al almacenamiento pnetworkingeterminado de los usuarios de la aplicación. Si está intentando escribir en NSUserDefaults cuando su aplicación está saliendo, su aplicación puede terminar antes de que la escritura se cometa en algunos escenarios. Terminar con fuerza usando exit(0) en su ejemplo es muy probable que estimule este comportamiento. Normalmente, cuando se sale de una aplicación, el sistema puede realizar la limpieza y esperar a que se completen las operaciones de file pendientes; cuando finaliza la aplicación con exit() o el depurador, esto puede no suceder.

En general, NSUserDefaults es confiable cuando se usa correctamente en iOS 8.

Estos cambios se describen en las notas de la versión de la Fundación para OS X 10.10 (actualmente no hay una nota de lanzamiento de la Fundación por separado para iOS 8).

Parece que iOS 8 no le gusta establecer cadenas en NSUserDefaults. Intente codificar la cadena en NSData antes de save.

Al save:

 [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:testString] forKey:@"Session"]; 

Al leer:

 NSData *_data = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSString *_dataArchive = [NSKeyedUnarchiver unarchiveObjectWithData:_data]; 

Espero que esto ayude.

Como dijo gnasher729, no llame a exit (). Puede haber un problema con NSUserDefaults en iOS8, pero la llamada exit () simplemente no funcionará.

Debería ver los comentarios de David Smith sobre NSUserDefaults ( https://gist.github.com/anonymous/8950927 ):

La terminación de una aplicación de forma anómala (la presión de memory mata, falla, se detiene en Xcode) es como reset de git –hard HEAD, y al salir

Encontré que NSUserDefaults se comportaba muy bien en iOS 8.4 cuando usaba un nombre de set para crear una instancia en lugar de confiar en standardUserDefaults .

NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"MySuiteName"];

Tengo el mismo problema con iOS 8 y la única solución que funcionó fue retrasar el performance de la function exit () de alguna duración (Ej .: 0.1 segundos) usando:

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), dispatch_get_main_queue(), ^{ exit(0); }); 

o cree un método y luego llámelo usando performSelector: withObject: afterDelay:

 - (void)exitApp { exit(0); } [self performSelector:@selector(exitApp) withObject:nil afterDelay:0.1]; 

Dado que se trata de una aplicación Enterprise y no una aplicación App Store, puede intentar:

 @interface UIApplication() -(void) _terminateWithStatus:(int)status; @end 

y luego llama:

 [UIApplication.shanetworkingApplication _terminateWithStatus:0]; 

Está utilizando una API no documentada, por lo que es posible que no funcione en versiones anteriores o futuras de iOS.

Es un error en simuladores. Este error también existe antes de iOS8 beta4 en dispositivos. Pero en los dispositivos este error se resuelve pero actualmente existe en simuladores. También han cambiado la estructura de directorys del simulador. Si reinicia su simulador, funcionará bien. .En dispositivos iOS8 también funcionará bien.

Lo encontré en Foundation Framework Reference, creo que será útil:

La class NSUserDefaults proporciona methods de conveniencia para acceder a types comunes, como flotantes, dobles, integers, booleans y URL. Un object pnetworkingeterminado debe ser una list de properties, es decir, una instancia de (o para collections una combinación de instancias de): NSData, NSString, NSNumber, NSDate, NSArray o NSDictionary. Si desea almacenar cualquier otro tipo de object, normalmente debería archivarlo para crear una instancia de NSData. Para get más información, consulte la Guía de progtwigción de preferences y configuraciones.

Como otros han señalado el uso de exit () y la salida general de su aplicación usted mismo es realmente una mala idea en iOS.

Pero probablemente sé de qué tienes que lidiar. También desarrollamos una aplicación empresarial y aunque intentamos convencer al cliente de que en iOS está en contra de todas las reglas y mejores prácticas, insistieron en que cerremos la aplicación en un momento dado.

En lugar de exit () usamos esta pieza de código:

 UIApplication *app = [UIApplication shanetworkingApplication]; [app performSelector:@selector(suspend)]; 

Como su nombre lo indica, solo suspende la aplicación como si el usuario presionara el button de inicio. Por lo tanto, sus methods de ahorro podrían terminar correctamente.

Sin embargo, no he probado esta solución para tu caso particular, y no estoy seguro si la suspensión es suficiente para ti, pero para nosotros funcionó.

He resuelto problemas similares al hacer cambios a NSUserDefaults solo en el hilo principal.

Me enfrenté al mismo problema. Lo solucioné llamando

[[NSUserDefaults standardUserDefaults] synchronize];

antes de llamar

[[NSUserDefaults standardUserDefaults] stringForKey:@"my_key"] .

Resulta que hay que llamar a la synchronize no solo después de la configuration, sino también antes de getla.

Llamar a exit () en una aplicación de iOS es un delito penal, y como se dio count, fue castigado. Nunca salgas de una aplicación de iOS tú mismo. Nunca.