NSUserDefaultsのNSMutableArrayにカスタムオブジェクトを格納する


101

最近、iPhoneアプリの検索結果をNSUserDefaultsコレクションに保存しようとしています。また、これを使用してユーザー登録情報を正常に保存しますが、何らかの理由でカスタムLocationクラスのNSMutableArrayを保存しようとすると、常に空に戻ります。

この投稿の時点でNSMutableArrayをNSData要素に変換しようとしましたが、同じ結果が得られます(iPhoneでNSUserDefaultsを使用して整数配列を保存することは可能ですか?

私が試したコードサンプルは次のとおりです。

保存する:

[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];

または

NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];

または

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

負荷:

lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];

または

NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);  

または

NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];

以下のアドバイスに従って、オブジェクトにNSCoderも実装しました(一時的なNSStringの過度の使用は無視してください)。

#import "Location.h"


@implementation Location

@synthesize locationId;
@synthesize companyName;
@synthesize addressLine1;
@synthesize addressLine2;
@synthesize city;
@synthesize postcode;
@synthesize telephoneNumber;
@synthesize description;
@synthesize rating;
@synthesize priceGuide;
@synthesize latitude;
@synthesize longitude;
@synthesize userLatitude;
@synthesize userLongitude;
@synthesize searchType;
@synthesize searchId;
@synthesize distance;
@synthesize applicationProviderId;
@synthesize contentProviderId;

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:locationId forKey:@"locationId"];
    [coder encodeObject:companyName forKey:@"companyName"];
    [coder encodeObject:addressLine1 forKey:@"addressLine1"];
    [coder encodeObject:addressLine2 forKey:@"addressLine2"];
    [coder encodeObject:city forKey:@"city"];
    [coder encodeObject:postcode forKey:@"postcode"];
    [coder encodeObject:telephoneNumber forKey:@"telephoneNumber"];
    [coder encodeObject:description forKey:@"description"];
    [coder encodeObject:rating forKey:@"rating"];
    [coder encodeObject:priceGuide forKey:@"priceGuide"];
    [coder encodeObject:latitude forKey:@"latitude"];
    [coder encodeObject:longitude forKey:@"longitude"];
    [coder encodeObject:userLatitude forKey:@"userLatitude"];
    [coder encodeObject:userLongitude forKey:@"userLongitude"];
    [coder encodeObject:searchType forKey:@"searchType"];
    [coder encodeObject:searchId forKey:@"searchId"];
    [coder encodeObject:distance forKey:@"distance"];
    [coder encodeObject:applicationProviderId forKey:@"applicationProviderId"];
    [coder encodeObject:contentProviderId forKey:@"contentProviderId"];

}

回答:


177

配列にカスタムオブジェクトを読み込む場合、これは配列を取得するために使用したものです。

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];
if (dataRepresentingSavedArray != nil)
{
    NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
    if (oldSavedArray != nil)
        objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
    else
        objectArray = [[NSMutableArray alloc] init];
}

ユーザーのデフォルトから返されたデータがnilでないことを確認する必要があります。nilからのアーカイブ解除はクラッシュを引き起こすと私は信じています。

次のコードを使用すると、アーカイブは簡単です。

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:@"savedArray"];

f3lixが指摘したように、カスタムオブジェクトをNSCodingプロトコルに準拠させる必要があります。次のようなメソッドを追加すると、うまくいくはずです。

- (void)encodeWithCoder:(NSCoder *)coder;
{
    [coder encodeObject:label forKey:@"label"];
    [coder encodeInteger:numberID forKey:@"numberID"];
}

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [super init];
    if (self != nil)
    {
        label = [[coder decodeObjectForKey:@"label"] retain];
        numberID = [[coder decodeIntegerForKey:@"numberID"] retain];
    }   
    return self;
}

3
initWithCoder:メソッドの最後に「return self」がありませんでした。これを追加すると、アーカイブ解除を実行できるようになります。
ブラッド・ラーソン

2
配列はnsuserdefaultsに対して大きすぎる可能性があるため、配列をnsuserdefaultsに格納しないでください。代わりに、+(BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)pathを使用してファイルに保存する必要があります。なぜこの回答はそれほど多く投票されたのですか?
mskw

1
@mskw-NSUserDefaultsを大きな配列の格納に使用しないでください(コアデータまたは生のSQLiteを代わりに使用する必要があります)が、エンコードされたオブジェクトの小さな配列をそこに隠しておくのはまったく問題ありません。いずれにせよ、質問は、これを行う方法を尋ねることでした。これは、上記のコードが提供するものです。投票に関する限り、あなたの推測は私のものと同じです。過去4年間で多くの人がこれをしたいと思ったに違いありません。
ブラッドラーソン

2
@Goodsum-いいえ、問題なく動作します。試してみてください。実際に変更可能なNSMutableArrayを返します。項目の配列を追加することで、新しい配列を開始するだけです。
ブラッド・ラーソン

1
@AlbertRenshaw- andSync上記のコードに追加したのはどこから来たのですか?これは、NSUserDefaultsの実際のメソッドシグネチャではありません。また、synchronizeかつてほど有用ではありません。twitter.com
Brad Larson

13

initWithCoderメソッドでエラーが発生したと思います。少なくとも、提供されたコードでは 'self'オブジェクトを返しません。

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }

    return self; // this is missing in the example above


}

私が使う

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

そして

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"lastResults"];
if (dataRepresentingSavedArray != nil)
{
        NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
        if (oldSavedArray != nil)
                objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
        else
                objectArray = [[NSMutableArray alloc] init];
}

そしてそれは私にとって完璧に動作します。

に関して、

ステファン


3
ステファン、あなたは命の恩人です、ここを見てください。「(自己= [スーパーのinit])場合は、」その行が私の心を救っstackoverflow.com/questions/1933285/...
fyasar

ERROR_BAD_ACCESSを取得しましたが、あなたは私を救いました。保持のみのプロパティを使用し、self.variableを追加することで解決しました。
デビッド

0

あなたのコードの問題は結果の配列を保存していないようです。そのデータをロードして使用してみてください

lastResults = [prefs arrayForKey:@"lastResults"];

これは、キーで指定された配列を返します。


0

「NSUserDefaultsがiPhone SDKにNSMutableDictionaryを保存できなかった理由」を参照してください(なぜNSUserDefaultsがiPhone SDKにNSMutableDictionaryを保存できなかったのですか?


カスタムオブジェクトを(非)シリアル化する場合は、データを(非)シリアル化する関数(NSCodingプロトコル)を提供する必要があります。配列はオブジェクトではなく連続したメモリのチャンクであるため、参照するソリューションはint配列で機能します。


私はあなたが正しいと思います(推奨されるリンキーがない限り)NSCodingを見てみましょう
Anthony Main

わかりましたので、上記のNSCodeを実装しましたが、それでもNSArchiverを使用する喜びはありません
Anthony Main

0

このようなものをdefaults dbに保存しようとしないことをお勧めします。

SQLiteはかなり簡単に入手して使用できます。スクリーンキャストの1つにエピソードがあります(http://pragprog.com/screencasts/v-bdiphone)の私が書いた簡単なラッパーに関するます(SCを購入しなくてもコードを入手できます)。

アプリ空間にアプリデータを保存する方がずっときれいです。

それでも、このデータをデフォルトのデータベースに入れても意味がある場合は、f3lixの投稿を参照してください。


1
提案に感謝しますが、いくつかの文字列を持つオブジェクトのためだけにSQLクエリ(例で見たように)を書き始める必要はありません
Anthony Main
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.