web-dev-qa-db-fra.com

NSUserDefaults perd ses clés et valeurs lorsque le téléphone est redémarré mais pas déverrouillé

Nous rencontrons actuellement le problème étrange suivant avec notre application iPhone. Comme le titre l'indique, NSUserDefaults perd nos clés et valeurs personnalisées lorsque le téléphone est redémarré mais pas déverrouillé, et cela se produit dans un scénario très spécifique.

Le contexte:

  • Nous utilisons le NSUserDefaults dans l'application pour stocker les données utilisateur (par exemple, le nom d'utilisateur).

  • Notre application a activé la localisation en mode fond.

  • Nous rencontrons ce problème uniquement lors de la distribution en direct ou par Testflight. Si je fais glisser et déposez le .ipa (le même qui a été distribué en direct) dans mon téléphone à l'aide de Xcode, je ne rencontre pas ce problème.

Situation: l'utilisateur installe l'application, se connecte et le nom d'utilisateur est correctement enregistré sur le NSUserDefaults. Ensuite, l'utilisateur éteint son appareil, le rallume et laisse le téléphone s'asseoir pendant un certain temps avant de déverrouiller l'écran.

Problème: si pendant ce temps un changement de lieu significatif est déclenché, l'application vient vivre en arrière-plan mais le NSUserDefaults est vide (ne contient que quelques clés de Apple mais aucun de nos ensuite, le NSUserDefaults ne récupère jamais ces clés, peu importe ce que vous faites (par exemple, si vous déverrouillez votre téléphone et ouvrez l'application, vous verrez que les clés sont toujours manquantes).

Toute aide ou idée sera vraiment appréciée :)

41
mp3821

Après un certain temps, Apple a reconnu cela comme un bug officiel. Il ne nous reste donc que différentes solutions de contournement jusqu'à ce qu'il soit résolu:

  1. Si vous avez besoin des données lors de l'exécution AVANT que le téléphone ne soit déverrouillé utilisez l'une des options suivantes et définissez le NSPersistentStoreFileProtectionKey = NSFileProtectionNone option:

    • Enregistrez les données à l'aide de Core Data. (Si vous devez accéder à la base de données en arrière-plan alors que le téléphone n'était pas encore déverrouillé ET que vous ne disposez pas d'informations sensibles, vous pouvez ajouter aux options Array l'option suivante: NSPersistentStoreFileProtectionKey = NSFileProtectionNone)
    • Utilisez le trousseau.
    • Utilisez un fichier .plist.
    • Utilisez des fichiers personnalisés: (par exemple: .txt avec un format spécifique).
    • Toute autre manière que vous pourriez trouver confortable pour stocker des données.

    Choisissez le vôtre;)

  2. Si vous n'avez pas besoin ou ne vous souciez pas des données avant que le téléphone ne soit déverrouillé vous pouvez utiliser cette approche (merci @maxf):

    Inscrivez-vous au applicationProtectedDataDidBecomeAvailable: notification et exécutez la ligne de code suivante dans le rappel [NSUserDefaults resetStandardUserDefaults]

    Cela vous fera NSUserDefault recharger juste après que votre téléphone a été autorisé à accéder aux données protégées, vous aidant ainsi à éviter complètement ce problème.

Merci à tous pour l'aide!

28
mp3821

J'avais un problème très similaire. Contexte de l'application. Utilisez d'autres applications gourmandes en mémoire jusqu'à ce que mon application soit supprimée de la mémoire. (Vous pouvez observer cet événement si votre appareil est branché et que xcode exécute la construction. Xcode vous dira que "l'application a été arrêtée en raison de la pression de la mémoire). D'ici, si votre application est enregistrée pour les événements de récupération en arrière-plan, elle se réveillera à certains pointer et se relancer mais en arrière-plan. À ce stade, si votre appareil est verrouillé, vos NSUserDefaults seront nuls.

Après avoir débogué ce cas pendant des jours, j'ai réalisé que ce n'était pas que NSUserDefaults était corrompu ou supprimé, c'était que l'application n'y avait pas accès en raison du verrouillage de l'appareil. Vous pouvez réellement observer ce comportement si vous essayez manuellement de télécharger le contenu de l'application via l'organisateur xcode, vous remarquerez que votre liste qui stocke les paramètres NSUserDefaults n'est pas présente si votre appareil reste verrouillé.

Ok donc NSUserDefaults n'est pas accessible si l'application est lancée en arrière-plan alors que l'appareil est verrouillé. Ce n'est pas grave, mais le pire est que, une fois l'application lancée en arrière-plan, elle reste en mémoire. À ce stade, si l'utilisateur déverrouille ensuite l'appareil et lance l'application au premier plan, vous n'avez toujours rien dans NSUserDefaults. En effet, une fois que l'application a chargé NSUserDefaults en mémoire (ce qui est nul), elle ne sait pas le recharger une fois que l'appareil est déverrouillé. synchronize ne fait rien dans ce cas. Ce que j'ai trouvé qui a résolu mon problème était d'appeler

[NSUserDefaults resetStandardUserDefaults] à l'intérieur de la méthode applicationProtectedDataDidBecomeAvailable.

J'espère que cela aide quelqu'un. Ces informations auraient pu me sauver de nombreuses heures de chagrin.

31
maxf

Nous avons également rencontré ce problème lors de l'utilisation d'un changement d'emplacement significatif, sur des appareils avec code d'accès activé. L'application se lance sur BG avant même que l'utilisateur ne déverrouille le code d'accès, et UserDefaults n'a rien.

Je pense qu'il est préférable de mettre fin à l'application avant la synchronisation, pour les raisons ci-dessous:

  • La synchronisation de UserDefaults ne doit pas être exécutée une fois après que UserDefaults a été effacé par ce bogue.
  • nous ne pouvons pas contrôler strictement l'appel de synchronisation car nous utilisons de nombreuses bibliothèques tierces.
  • l'application ne fera aucun bien si UserDefaults ne peut pas être chargé (même avant que l'utilisateur ne passe le verrouillage du code).

Voici donc notre solution de contournement (un peu bizarre). L'application se tue immédiatement lorsque la situation (état de l'application = BG, UserDefaults est effacée, iOS> = 7) détectée.

Il ne doit pas violer la norme UX, car la fin de l'application en arrière-plan ne sera même pas remarquée par l'utilisateur. (Et cela se produit également avant que l'utilisateur ne passe même la validation du code d'accès)

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

+ (void)crashIfUserDefaultsIsInBadState
{
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")
        && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
        if ([[NSUserDefaults standardUserDefaults] objectForKey:@"firstBootDate"]) {
            NSLog(@"------- UserDefaults is healthy now.");
        } else {
            NSLog(@"----< WARNING >--- this app will terminate itself now, because UserDefaults is in bad state and not recoverable.");
            exit(0);
        }
    }
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"firstBootDate"];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self.class crashIfUserDefaultsIsInBadState]; // need to put this on the FIRST LINE of didFinishLaunchingWithOptions

    ....
}
2
makoto

C'est toujours le comportement sur IOS 9.0 et depuis IOS 7.0.

Je soupçonne que Apple ne changera pas cela, car c'est une conséquence du fait que le .plist les charges [NSUserDefaults standardUserDefaults] est protégé avec NSFileProtectionCompleteUntilFirstUserAuthentication.

Voir aussi Pourquoi NSUserDefaults n'est-il pas lu après une batterie déchargée IOS7

1
Jacob Wallström