web-dev-qa-db-ja.com

NSUserDefaultsは、電話機を再起動してもロック解除しないと、キーと値を失います

現在、iPhoneアプリで次の奇妙な問題が発生しています。タイトルが示すように、NSUserDefaultsは、電話が再起動されたがロック解除されていない場合、カスタムキーと値を失っています。これは非常に特殊なシナリオで発生しています。

環境:

  • アプリではNSUserDefaultsを使用してユーザーデータ(ユーザー名など)を保存しています。

  • アプリのバックグラウンドモードで位置情報が有効になっています。

  • この問題は、無線またはTestflightで配布する場合にのみ発生します。 Xcodeを使用して.ipa(無線で配布されたものと同じ)を電話にドラッグアンドドロップしても、この問題は発生しません。

状況:ユーザーがアプリをインストールしてログインし、ユーザー名がNSUserDefaultsに正常に保存されます。次に、ユーザーはデバイスをオフにしてから再びオンにし、画面のロックを解除する前にしばらく電話を置いておきます。

問題:その時点で重要な場所の変更がトリガーされた場合、アプリはバックグラウンドで動作しますが、NSUserDefaultsは空です(Appleの一部のキーしかありませんが、その後、NSUserDefaultsは何をしてもこのキーを回復することはありません(たとえば、スマートフォンのロックを解除してアプリを開くと、キーがまだ見つからないことがわかります)。

どんな助けやアイデアでも本当に感謝します:)

41
mp3821

しばらくすると、Appleはこれを公式のバグとして認識しました。そのため、解決されるまでは別の回避策しか残されていません。

  1. 電話がロック解除される前に実行中にデータが必要な場合次のオプションのいずれかを使用し、NSPersistentStoreFileProtectionKey = NSFileProtectionNoneオプションを設定します。

    • Core Dataを使用してデータを保存します。 (まだ電話のロックが解除されていないときにバックグラウンドでDBにアクセスする必要があり、それに適切な情報がない場合は、options配列に次のオプションを追加できます:NSPersistentStoreFileProtectionKey = NSFileProtectionNone
    • キーチェーンを使用します。
    • .plistファイルを使用します。
    • カスタムメイドのファイルを使用します(例:特定の形式の.txt)。
    • データを保存するのに適していると思われるその他の方法。

    あなたのものを選択してください;)

  2. あなたが電話がロック解除される前のデータを必要としない、または気にしないあなたはこのアプローチを使用できます(ありがとう@maxf):

    applicationProtectedDataDidBecomeAvailable:通知に登録し、コールバック[NSUserDefaults resetStandardUserDefaults]内で次のコード行を実行します

    これにより、保護されたデータへのアクセス許可が携帯電話に付与された直後にNSUserDefaultがリロードされ、この問題を完全に回避するのに役立ちます。

助けてくれてありがとう!

28
mp3821

私は非常に類似した問題を抱えていました。アプリケーションの背景。アプリケーションがメモリから解放されるまで、他のメモリ負荷の高いアプリケーションを使用します。 (デバイスが接続されていて、xcodeがビルドを実行している場合は、このイベントを確認できます。Xcodeは、「メモリ不足のためにアプリケーションが終了しました」と通知します。ここから、アプリケーションがバックグラウンドフェッチイベントに登録されている場合、ある時点でウェイクアップします。ポイントして再起動しますが、バックグラウンドに戻ります。この時点でデバイスがロックされている場合、NSUserDefaultsはnullになります。

このケースを数日間デバッグした後、NSUserDefaultsが破損したり、不正に使用されたりしているのではなく、デバイスがロックされているためにアプリケーションがアクセスできないことがわかりました。 xcodeオーガナイザーを介してアプリケーションのコンテンツを手動でダウンロードしようとすると、この動作を実際に確認できます。デバイスがロックされたままの場合、NSUserDefaults設定を保存するplistが存在しないことに気付くでしょう。

デバイスがロックされているときにアプリケーションがバックグラウンドで起動された場合は、NSUserDefaultsにアクセスできません。大したことではありませんが、最悪の部分は、アプリケーションがバックグラウンドで起動されると、メモリ内にとどまります。この時点で、ユーザーがデバイスのロックを解除してアプリケーションをフォアグラウンドで起動した場合、NSUserDefaultsには何もありません。これは、アプリケーションがNSUserDefaultsをメモリ(nullである)にロードすると、デバイスがロック解除されると、それを再ロードすることを認識できないためです。この場合、同期は何もしません。私の問題を解決したと私が見つけたのは

[NSUserDefaults resetStandardUserDefaults]applicationProtectedDataDidBecomeAvailableメソッド内。

これが誰かを助けることを願っています。この情報があれば、私は何時間も悲しみを救うことができました。

31
maxf

パスコードが有効になっているデバイスで、大幅な場所の変更を使用した場合にも、この問題が発生しました。ユーザーがパスコードのロックを解除する前にアプリが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

    ....
}
2
makoto

これは、まだIOS 9.0での動作であり、IOS 7.0以降です。

Appleはこれを変更しないと思います。これは、[NSUserDefaults standardUserDefaults]がロードする.plistがNSFileProtectionCompleteUntilFirstUserAuthenticationで保護されているためです。

参照 電池残量がIOS7になった後にNSUserDefaultsが読み込まれないのはなぜですか

1