Подтвердить что ты не робот

NSUserDefaults ненадежна в iOS 8

У меня есть приложение, которое использует [NSUserDefaults standardUserDefaults] для хранения информации о сеансе. Как правило, эта информация проверяется при запуске приложения и обновляется при выходе приложения. Я обнаружил, что, похоже, он работает ненадежно в iOS 8.

В настоящее время я тестирую iPad 2, хотя, если нужно, я могу протестировать другие устройства.

Некоторое время данные, записанные перед выходом, не будут сохраняться при запуске приложения. В то же время ключи, удаленные до выхода, иногда появляются после запуска.

Я написал следующий пример, чтобы попытаться проиллюстрировать проблему:

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

Запустив вышеприведенный код, я (обычно) получаю вывод ниже (что и следовало ожидать):

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

Если я затем закомментирую строки, которые удаляют ключ:

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

И запустите приложение три раза, я бы ожидал увидеть:

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

Но на самом деле я вижу:

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

например. Кажется, что не нужно обновлять значение при выходе из приложения.

EDIT. Я тестировал один и тот же код на iPad 2 под управлением iOS 7.1.2; и он, кажется, работает правильно каждый раз.

TL;DR. В iOS 8 работает [NSUserDefaults standardUserDefaults] неудовлетворительно? И если это так обходное решение/решение?

4b9b3361

Ответ 1

iOS 8 ввел ряд изменений поведения в NSUserDefaults. Хотя API NSUserDefaults мало изменился, поведение изменилось таким образом, который может иметь отношение к вашему приложению. Например, использование -synchronize не рекомендуется (и всегда было). Изменения добавлений в другие части Foundation и CoreFoundation, такие как Координация файлов и изменения, связанные с общими контейнерами, могут повлиять на ваше приложение и ваше использование NSUserDefaults.

В связи с этим изменилось письмо на NSUserDefaults. Запись занимает больше времени, и могут быть другие процессы, конкурирующие за доступ к хранилищу данных по умолчанию для пользователей приложения. Если вы пытаетесь записать на NSUserDefaults по мере выхода вашего приложения, ваше приложение может быть прекращено до того, как запись будет зафиксирована в некоторых сценариях. Сильно прекратить использование exit(0) в вашем примере очень вероятно, чтобы стимулировать это поведение. Обычно, когда приложение выходит из системы, система может выполнять очистку и ждать завершения выдающихся операций с файлами - когда вы завершаете приложение с помощью exit() или отладчика, этого может не случиться.

В целом NSUserDefaults является надежным при правильном использовании на iOS 8.

Эти изменения описаны в Примечания к выпуску Foundation для OS X 10.10 (в настоящее время для iOS 8 нет отдельной заметки о выпуске Foundation).

Ответ 2

Похоже, что iOS 8 не нравится устанавливать строки в NSUserDefaults. Попробуйте кодировать строку в NSData перед сохранением.

При сохранении:

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

При чтении:

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

Надеюсь, что это поможет.

Ответ 3

Как сказал gnasher729, не вызывайте exit(). Может возникнуть проблема с NSUserDefaults в iOS8, но вызов exit() просто не работает.

Вы должны увидеть комментарии Дэвида Смита о NSUserDefaults (https://gist.github.com/anonymous/8950927):

Прекращение приложения ненормально (уменьшение давления памяти, сбой, остановка в Xcode) похоже на git reset --hard HEAD и оставляя

Ответ 4

Я нашел NSUserDefaults хорошо себя вести на iOS 8.4 при использовании имени набора для создания экземпляра вместо того, чтобы полагаться на standardUserDefaults.

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

Ответ 5

У меня такая же проблема с iOS 8, и единственным решением для меня было отложить выполнение функции exit() некоторой продолжительностью (пример: 0,1 секунды), используя:

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

или создать метод, а затем вызвать его с помощью выполнитьSelector: withObject: afterDelay:

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

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

Ответ 6

Поскольку это приложение Enterprise, а не приложение для App Store, вы можете попробовать:

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

а затем вызовите:

[UIApplication.sharedApplication _terminateWithStatus:0];

Он использует недокументированный API, поэтому может не работать в предыдущих версиях iOS.

Ответ 7

Это ошибка в симуляторах. Эта ошибка также существует до iOS8 beta4 на устройствах. Но на устройствах эта ошибка устранена, но в настоящее время она существует на симуляторах. Они также изменили структуру каталогов симуляторов. Если вы reset ваш симулятор будет работать отлично. На устройствах iOS8 он также будет работать нормально.

Ответ 8

Я нашел его в справочнике Foundation Framework, думаю, что это будет полезно:

Класс NSUserDefaults предоставляет удобные методы для доступа общие типы, такие как float, double, integers, Booleans и URL. объект по умолчанию должен быть списком свойств, то есть экземпляром (или для коллекций - комбинация экземпляров): NSData, NSString, NSNumber, NSDate, NSArray или NSDictionary. Если вы хотите сохранить другой тип объекта, вы должны, как правило, архивировать его для создания экземпляр NSData.. Дополнительные сведения см. в разделе "Настройки и настройки". Руководство по программированию.

Ответ 9

Как отмечали другие, использование exit() и вообще выход из вашего приложения сами по себе являются плохими идеями в iOS.

Но Я, вероятно, знаю, с чем вам приходится иметь дело. Мы также разработали корпоративное приложение, и хотя мы пытались убедить клиента в том, что в iOS он противоречит всем правилам и передовым методам, они настаивали на том, чтобы закрыть приложение в какой-то момент.

Вместо exit() мы использовали этот фрагмент кода:

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

Как видно из названия, оно приостанавливает приложение, как если бы пользователь нажал кнопку "домой". Поэтому ваши методы сохранения могут быть закончены правильно.

Я не тестировал это решение для вашего конкретного случая, но я не уверен, что для вас достаточно приостановки, но для нас это сработало.

Ответ 10

Я решил подобные проблемы, внеся изменения в NSUserDefaults только в основном потоке.

Ответ 11

Я столкнулся с той же проблемой. Я решил это, позвонив

[[NSUserDefaults standardUserDefaults] synchronize];

перед вызовом

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

Оказывается, нужно называть synchronize не только после настройки, но и перед тем, как получить слишком.

Ответ 12

Вызов exit() в приложении iOS является уголовным преступлением, и, как вы заметили, он получил наказание. Вы никогда не покидаете приложение iOS самостоятельно. Никогда.