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] неудовлетворительно? И если это так обходное решение/решение?
Ответы
Ответ 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 самостоятельно. Никогда.