[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];
アプリを3回実行すると、次のように表示されます。
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 [〜#〜]:iOS 7.1.2を実行しているiPad 2で同じコードをテストしました。毎回正しく動作するようです。
TLDR-iOS 8では、[NSUserDefaults standardUserDefaults]は信頼性の低い動作をしますか?動作する場合、回避策/解決策はありますか?
iOS 8では、NSUserDefaults
にいくつかの動作の変更が導入されました。 NSUserDefaults
APIはほとんど変更されていませんが、動作はアプリケーションに関連する方法で変更されています。たとえば、-synchronize
の使用は推奨されていません(常に推奨されています)。ファイル調整などのFoundationおよびCoreFoundationの他の部分への追加変更、および共有コンテナーに関連する変更は、アプリケーションとNSUserDefaults
の使用に影響する場合があります。
このため、特にNSUserDefaults
への書き込みが変更されました。書き込みには時間がかかり、アプリケーションのユーザーデフォルトストレージへのアクセスを競合する他のプロセスが存在する場合があります。アプリケーションの終了時にNSUserDefaults
に書き込もうとすると、いくつかのシナリオで書き込みがコミットされる前にアプリケーションが終了する場合があります。この例でexit(0)
を使用して強制的に終了すると、この動作が刺激される可能性が非常に高くなります。通常、アプリケーションが終了すると、システムはクリーンアップを実行し、未処理のファイル操作が完了するのを待つことができます-exit()
またはデバッガーを使用してアプリケーションを終了する場合、これは発生しません。
一般的に、NSUserDefaults
は、iOS 8で正しく使用すると信頼できます。
これらの変更については、 OS X 10.10のFoundationリリースノート に記載されています(現在、iOS 8用の個別のFoundationリリースノートはありません)。
IOS 8はNSUserDefaultsで文字列を設定することを好まないようです。保存する前に、文字列をNSDataにエンコードしてみてください。
保存するとき:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:testString] forKey:@"Session"];
読むとき:
NSData *_data = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"];
NSString *_dataArchive = [NSKeyedUnarchiver unarchiveObjectWithData:_data];
お役に立てれば。
Gnasher729が言ったように、exit()を呼び出さないでください。 iOS8のNSUserDefaultsには問題があるかもしれませんが、exit()を呼び出すだけでは機能しません。
NSUserDefaultsに関するDavid Smithのコメントが表示されます( https://Gist.github.com/anonymous/8950927 ):
アプリを異常終了する(メモリプレッシャーの強制終了、クラッシュ、Xcodeでの停止)は、git reset --hard HEADのようなもので、終了します
NSUserDefaultsは、standardUserDefaults
に依存する代わりにスイート名を使用してインスタンスを作成するときに、iOS 8.4で適切に動作することがわかりました。
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"MySuiteName"];
私はiOS 8で同じ問題を抱えており、私のために働いた唯一の解決策は、exit()関数の実行をいくつかの期間(例:0.1秒)遅らせることです:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), dispatch_get_main_queue(), ^{ exit(0); });
または、メソッドを作成してからperformSelector:withObject:afterDelay:を使用して呼び出します
- (void)exitApp {
exit(0);
}
[self performSelector:@selector(exitApp) withObject:nil afterDelay:0.1];
Foundation Framework Referenceで見つけましたが、役に立つと思います:
NSUserDefaultsクラスは、float、double、integer、Boolean、URLなどの一般的な型にアクセスするための便利なメソッドを提供します。デフォルトオブジェクトは、プロパティリスト、つまり、NSData、NSString、NSNumber、NSDate、NSArray、またはNSDictionaryのインスタンス(またはコレクションの場合はインスタンスの組み合わせ)でなければなりません。 他の種類のオブジェクトを保存する場合、通常はNSDataのインスタンスを作成するためにそれをアーカイブする必要があります。詳細については、「設定と設定のプログラミングガイド」を参照してください。
私は同じ問題に直面しました。私は電話で解決しました
[[NSUserDefaults standardUserDefaults] synchronize];
呼び出す前に
[[NSUserDefaults standardUserDefaults] stringForKey:@"my_key"]
。
設定後だけでなく、取得する前にsynchronize
を呼び出す必要があることがわかります。
メインスレッドでのみNSUserDefaultsを変更することで、同様の問題を解決しました。
これはエンタープライズアプリであり、App Storeアプリではないため、以下を試すことができます。
@interface UIApplication()
-(void) _terminateWithStatus:(int)status;
@end
次に呼び出します:
[UIApplication.sharedApplication _terminateWithStatus:0];
文書化されていないAPIを使用しているため、iOSの以前または将来のバージョンでは動作しない可能性があります。
他の人がexit()を使用して指摘し、一般的にアプリを終了することはiOSでは本当に悪い考えです。
しかし私はおそらくあなたが何に対処しなければならないか知っています。エンタープライズアプリケーションも開発しましたが、iOSではすべてのルールとベストプラクティスに違反しているとクライアントに納得させようとしましたが、彼らはある時点でアプリを閉じることを主張しました。
Exit()の代わりに、次のコードを使用しました。
UIApplication *app = [UIApplication sharedApplication];
[app performSelector:@selector(suspend)];
名前が示すように、ユーザーがホームボタンを押したかのようにアプリを一時停止するだけです。したがって、保存方法は正しく終了できる場合があります。
ただし、特定のケースに対してこのソリューションをテストしたことはありません。サスペンドで十分かどうかはわかりませんが、私たちにとってはうまくいきました。
これは、シミュレーターのバグです。このバグは、デバイス上のiOS8 beta4以前にも存在しますが、デバイス上ではこのバグは解決されていますが、現在シミュレーター上に存在しています。シミュレーターのディレクトリ構造も変更されています。 iOS8デバイスでも正常に動作します。