web-dev-qa-db-fra.com

NSUserDefaults non fiable dans iOS 8

J'ai une application qui utilise [NSUserDefaults standardUserDefaults] pour stocker les informations de session. Généralement, ces informations sont vérifiées au lancement de l'application et mises à jour à la sortie de l'application. J'ai constaté qu'il semble ne pas fonctionner de manière fiable dans iOS 8.

Je teste actuellement sur un iPad 2, bien que je puisse tester sur d'autres appareils si besoin est.

Parfois, les données écrites avant la sortie ne persisteront pas au lancement de l'application. De même, les clés supprimées avant la sortie semblent parfois exister après le lancement.

J'ai écrit l'exemple suivant, pour essayer d'illustrer le problème:

- (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);
}

En exécutant le code ci-dessus, j'obtiens (généralement) la sortie ci-dessous (ce que j'attendrais):

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

Si je commente ensuite les lignes qui suppriment la clé:

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

Et exécutez l'application trois fois, je m'attends à voir:

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

Mais la sortie que je vois en fait est:

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

par exemple. Il semble ne pas mettre à jour la valeur en quittant l'application.

MODIFIER : J'ai testé le même code sur un iPad 2 exécutant iOS 7.1.2; et il semble fonctionner correctement à chaque fois.

TLDR - Dans iOS 8, [NSUserDefaults standardUserDefaults] ne fonctionne-t-il pas de manière fiable? Et si oui, existe-t-il une solution/solution?

32
HaemEternal

iOS 8 a introduit un certain nombre de changements de comportement dans NSUserDefaults. Bien que l'API NSUserDefaults ait peu changé, le comportement a changé d'une manière qui peut être pertinente pour votre application. Par exemple, l'utilisation de -synchronize Est déconseillée (et l'a toujours été). L'ajout de modifications à d'autres parties de Foundation et CoreFoundation telles que la coordination de fichiers et les modifications liées aux conteneurs partagés peuvent affecter votre application et votre utilisation de NSUserDefaults.

L'écriture dans NSUserDefaults en particulier a changé à cause de cela. L'écriture prend plus de temps et d'autres processus peuvent être en concurrence pour accéder au stockage par défaut de l'utilisateur de l'application. Si vous essayez d'écrire dans NSUserDefaults pendant la fermeture de votre application, votre application peut être arrêtée avant que l'écriture ne soit validée dans certains scénarios. L'arrêt forcé de l'utilisation de exit(0) dans votre exemple est très susceptible de stimuler ce comportement. Normalement, lorsqu'une application est fermée, le système peut effectuer un nettoyage et attendre la fin des opérations sur les fichiers en attente - lorsque vous arrêtez l'application à l'aide de exit() ou du débogueur, cela peut ne pas se produire.

En général, NSUserDefaults est fiable lorsqu'il est utilisé correctement sur iOS 8.

Ces modifications sont décrites dans Notes de version Foundation pour OS X 10.1 (actuellement, il n'y a pas de note de version Foundation distincte pour iOS 8).

14
quellish

Il semble que iOS 8 n'aime pas définir des chaînes dans NSUserDefaults. Essayez de coder la chaîne dans NSData avant de l'enregistrer.

Lors de l'enregistrement:

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

Lors de la lecture:

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

J'espère que cela t'aides.

9
Igor

Comme l'a dit gnasher729, n'appelez pas exit (). Il peut y avoir un problème avec NSUserDefaults dans iOS8, mais appeler exit () ne fonctionnera tout simplement pas.

Vous devriez voir les commentaires de David Smith sur NSUserDefaults ( https://Gist.github.com/anonymous/8950927 ):

Mettre fin à une application de manière anormale (destruction de la pression de la mémoire, plantage, arrêt dans Xcode) est comme git reset --hard HEAD, et quitter

3
n-b

J'ai trouvé que NSUserDefaults se comportait bien sur iOS 8.4 lors de l'utilisation d'un nom de suite pour créer une instance au lieu de s'appuyer sur standardUserDefaults.

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

2
Thomas Verbeek

J'ai le même problème avec iOS 8 et la seule solution qui a fonctionné pour moi est de retarder l'exécution de la fonction exit () d'une durée (Ex .: 0,1 seconde) en utilisant:

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

ou créez une méthode, puis appelez-la à l'aide de performSelector: withObject: afterDelay:

- (void)exitApp {
   exit(0);
}

[self performSelector:@selector(exitApp) withObject:nil afterDelay:0.1];
1
Basem Saadawy

Je l'ai trouvé dans Foundation Framework Reference, je pense qu'il sera utile:

La classe NSUserDefaults fournit des méthodes pratiques pour accéder aux types courants tels que les flottants, les doubles, les entiers, les booléens et les URL. Un objet par défaut doit être une liste de propriétés, c'est-à-dire une instance de (ou pour les collections une combinaison d'instances de): NSData, NSString, NSNumber, NSDate, NSArray ou NSDictionary. Si vous souhaitez stocker tout autre type d'objet, vous devez généralement l'archiver pour créer une instance de NSData. Pour plus de détails, consultez le Guide de programmation des préférences et paramètres.

0
wm.p1us

J'ai fait face au même problème. Je l'ai résolu en appelant

[[NSUserDefaults standardUserDefaults] synchronize];

avant d'appeler

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

Il s'avère que l'on doit appeler synchronize non seulement après le réglage mais avant de le faire aussi.

0
fnc12

J'ai résolu des problèmes similaires en apportant des modifications à NSUserDefaults uniquement dans le thread principal.

0
Radu Simionescu

Comme il s'agit d'une application d'entreprise et non d'une application de l'App Store, vous pouvez essayer:

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

puis appelez:

[UIApplication.sharedApplication _terminateWithStatus:0];

Il utilise une API non documentée et peut donc ne pas fonctionner dans les versions précédentes ou futures d'iOS.

0
EricS

Comme d'autres l'ont souligné, utiliser exit () et généralement quitter votre application vous-même est vraiment une mauvaise idée dans iOS.

Mais Je sais probablement à quoi vous devez faire face. Nous avons également développé une application d'entreprise et même si nous avons essayé de convaincre le client que dans iOS, elle est contraire à toutes les règles et meilleures pratiques, ils ont insisté pour que nous fermions l'application à un moment donné.

Au lieu de exit (), nous avons utilisé ce morceau de code:

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

Comme son nom l'indique, il ne suspend l'application que si l'utilisateur a appuyé sur le bouton d'accueil. Par conséquent, vos méthodes d'enregistrement peuvent être en mesure de se terminer correctement.

Je n'ai pas testé cette solution pour votre cas particulier, et je ne sais pas si la suspension vous suffit, mais pour nous, cela a fonctionné.

0
micromanc3r

C'est un bug dans les simulateurs.Ce bug existe également avant iOS8 beta4 sur les appareils.Mais sur les appareils, ce bug est résolu mais il existe actuellement sur les simulateurs.Ils ont également changé la structure des répertoires du simulateur.Si vous réinitialisez votre simulateur, cela fonctionnera correctement Sur les appareils iOS8, cela fonctionnera également très bien.

0
Waseem Shah