NSUserDefaults потеряет свои ключи и значения, когда телефон перезагружен, но не разблокирован

В настоящее время мы испытываем следующую странную проблему с нашим iPhone-приложением. Как гласит название, NSUserDefaults теряет наши пользовательские ключи и значения, когда телефон перезагружается, но не разблокирован, и это происходит по очень специфическому сценарию.

Контекст:

  • Мы используем NSUserDefaults в приложении для хранения пользовательских данных (например, имя пользователя).

  • В нашем приложении включено разрешение в фоновом режиме.

  • Мы испытываем эту проблему только при распределении по воздуху или через Testflight. Если я перетащил .ipa(то же, что был распределен по воздуху) в свой телефон, используя Xcode, я не испытываю этой проблемы.

Ситуация: пользователь устанавливает приложение, регистрируется и имя пользователя сохраняется на NSUserDefaults успешно. Затем пользователь выключает их устройство и включает его обратно и позволяет телефону сидеть в течение некоторого времени, прежде чем разблокировать экран.

Проблема: если за это время произошло значительное изменение местоположения, приложение переходит в фоновом режиме, но NSUserDefaults пуст (у него есть только некоторые клавиши из яблока, но ни один из наших пользовательских ключей). Затем NSUserDefaults никогда не восстанавливает эти ключи независимо от того, что вы делаете (например, если вы разблокируете свой телефон и откройте приложение, вы увидите, что ключи все еще отсутствуют).

Любая помощь или идея будут по достоинству оценены:)

Ответ 1

Через некоторое время Apple признала это официальной ошибкой. Поэтому мы остаемся с разными обходными решениями до тех пор, пока не решим:

  • Если вы нуждаетесь в данных во время выполнения перед тем, как телефон разблокирована, используйте один из следующих параметров и установите параметр NSPersistentStoreFileProtectionKey = NSFileProtectionNone:

    • Сохранение данных с использованием основных данных. (Если вам нужно получить доступ к БД в
      когда телефон еще не был разблокирован, и у вас нет разумная информация в нем, вы можете добавить в опции Array
      следующая опция: NSPersistentStoreFileProtectionKey =
      NSFileProtectionNone
      )
    • Используйте брелок.
    • Используйте файл .plist.
    • Использовать созданные на заказ файлы: (например:.txt с определенным форматом).
    • Любой другой способ, которым может быть удобно хранить данные.

    Выберите свой;)

  • Если вы не нуждаетесь или не заботитесь о данных перед телефоном   был разблокирован, вы можете использовать этот подход (спасибо @maxf):

Зарегистрируйте уведомление applicationProtectedDataDidBecomeAvailable: и выполните следующую строку кода внутри обратного вызова [NSUserDefaults resetStandardUserDefaults]

Это приведет к перезагрузке NSUserDefault сразу после того, как вашему телефону будет предоставлено разрешение на доступ к защищенным данным, что поможет вам полностью избежать этой проблемы.

Спасибо всем за помощь!

Ответ 2

У меня была очень похожая проблема. Обоснование приложения. Используйте другие приложения с большой памятью, пока мое приложение не будет выброшено из памяти. (Вы можете наблюдать это событие, если вы подключили свое устройство и xcode запускаете сборку. Xcode скажет вам, что "приложение было прекращено из-за давления памяти" ). Здесь, если ваше приложение зарегистрировано для событий фоновой выборки, оно проснется при некоторых и заново, но в фоновом режиме. В этот момент, если ваше устройство заблокировано, ваши NSUserDefaults будут пустыми.

После отладки этого случая в течение нескольких дней я понял, что это не то, что NSUserDefaults был поврежден или отключен, но приложение не имеет доступа к нему из-за блокировки устройства. Вы можете наблюдать это поведение, если вручную попытаетесь загрузить содержимое приложения через организатор xcode, вы заметите, что ваш plist, который хранит настройки NSUserDefaults, отсутствует, если ваше устройство остается заблокированным.

Итак, NSUserDefaults недоступен, если приложение запущено в фоновом режиме, когда устройство заблокировано. Неважно, но в худшем случае, когда приложение запускается в фоновом режиме, оно остается в памяти. В этот момент ЕСЛИ пользователь затем разблокирует устройство и запускает приложение на передний план, у вас STILL нет ничего внутри NSUserDefaults. Это связано с тем, что, как только приложение загрузило NSUserDefaults в память (которое равно null), оно не знает, чтобы перезагрузить его, как только устройство разблокируется. syn synchron ничего не делает в этом случае. То, что я обнаружил, что решить мою проблему, вызвало

[NSUserDefaults resetStandardUserDefaults] внутри метода applicationProtectedDataDidBecomeAvailable.

Надеюсь, это поможет кому-то. Эта информация могла бы сэкономить мне много часов горя.

Ответ 3

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

Я думаю, что лучше прекратить действие приложения до того, как произойдет синхронизация, потому что причины ниже:

  • Синхронизация UserDefaults не должна выполняться один раз после очистки UserDefaults этой ошибкой.
  • мы не можем строго контролировать вызов синхронизации, потому что мы используем многие сторонние библиотеки.
  • приложение не принесет пользы, если UserDefaults не могут быть загружены (даже до того, как пользователь выполнит блокировку паролей).

Итак, вот наш (немного странный) обходной путь. Приложение немедленно убивает, когда обнаружена ситуация (состояние приложения = BG, UserDefaults очищено, iOS >= 7).

Он не должен нарушать стандарт UX, потому что приложение, закрывающее приложение, не будет даже замечено пользователем. (И также это происходит до того, как пользователь даже проведет проверку кода прохода)

#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

    ....
}