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

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

контекст:

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

  • наше приложение имеет местоположение включено на фоне Режим.

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

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

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

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

4 ответов


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

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

Ok так NSUserDefaults недоступен, если приложение запускается в фоновом режиме, когда устройство заблокировано. Не большое дело, но худшая часть этого, как только приложение запускается в фоновом режиме он остается в памяти. На данный момент, если пользователь затем разблокирует устройство и запускает приложение на передний план, у вас по-прежнему нет ничего внутри NSUserDefaults. Это связано с тем, что после того, как приложение загрузило NSUserDefaults в память (что равно null), оно не знает, чтобы перезагрузить его как только устройство будет разблокировано. синхронизировать ничего не делает в этом случае. То, что я нашел, решило мою проблему, - это вызов

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

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


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

  1. если вы нужны данные во время выполнения, Прежде чем телефон имеет был разблокирован используйте один из следующих вариантов и выберите :

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

    выбрать ваш ;)

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

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

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

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


мы также столкнулись с этой проблемой при использовании значительного изменения местоположения на устройствах с включенным кодом прохода. Приложение запускается на 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

    ....
}

Это все еще поведение на IOS 9.0 и было начиная с IOS 7.0.

Я подозреваю, что Apple не изменит этого, так как это следствие того, что .plist нагрузки [NSUserDefaults standardUserDefaults] защищены NSFileProtectionCompleteUntilFirstuserauthentication.

см. также почему NSUserDefaults не читаются после плоской батареи IOS7