NSUserDefaultsにカスタムオブジェクトを保存する方法


268

さて、私はいくつかのことをやっています、そして私は自分の問題に気付きましたが、それを修正する方法がわかりません。一部のデータを保持するカスタムクラスを作成しました。私はこのクラスのオブジェクトを作成し、セッション間で持続させる必要があります。すべての情報をに入れる前はNSUserDefaults、これは機能しません。

-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.

これは、カスタムクラス「Player」をに配置したときに表示されるエラーメッセージNSUserDefaultsです。今、私は明らかにNSUserDefaultsいくつかのタイプの情報しか保存していないことを読みました。それで、どのようにしてオブジェクトを入れNSUSerDefaultsますか?

カスタムオブジェクトを "エンコード"して配置する方法があるはずだと読みましたが、実装方法がわからないので、助けていただければ幸いです。ありがとうございました!

****編集****

承知しました。以下のコードを使用して作業を行いました(ありがとうございます)。しかし、まだ問題がいくつかあります。基本的に、コードは今クラッシュしますが、エラーが発生しないため、理由はわかりません。おそらく、基本的なものが欠けていて、疲れすぎているかもしれませんが、それはわかります。これが私のカスタムクラス「Player」の実装です。

@interface Player : NSObject {
    NSString *name;
    NSNumber *life;
    //Log of player's life
}
//Getting functions, return the info
- (NSString *)name;
- (int)life;


- (id)init;

//These are the setters
- (void)setName:(NSString *)input; //string
- (void)setLife:(NSNumber *)input; //number    

@end

実装ファイル:

#import "Player.h"
@implementation Player
- (id)init {
    if (self = [super init]) {
        [self setName:@"Player Name"];
        [self setLife:[NSNumber numberWithInt:20]];
        [self setPsnCounters:[NSNumber numberWithInt:0]];
    }
    return self;
}

- (NSString *)name {return name;}
- (int)life {return [life intValue];}
- (void)setName:(NSString *)input {
    [input retain];
    if (name != nil) {
        [name release];
    }
    name = input;
}
- (void)setLife:(NSNumber *)input {
    [input retain];
    if (life != nil) {
        [life release];
    }
    life = input;
}
/* This code has been added to support encoding and decoding my objecst */

-(void)encodeWithCoder:(NSCoder *)encoder
{
    //Encode the properties of the object
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.life forKey:@"life"];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if ( self != nil )
    {
        //decode the properties
        self.name = [decoder decodeObjectForKey:@"name"];
        self.life = [decoder decodeObjectForKey:@"life"];
    }
    return self;
}
-(void)dealloc {
    [name release];
    [life release];
    [super dealloc];
}
@end

これが私のクラスです。非常に単純明快です。オブジェクトを作成するのに役立つことは知っています。そこで、AppDelegateファイルの関連部分(暗号化関数と復号化関数を呼び出す場所)を次に示します。

@class MainViewController;

@interface MagicApp201AppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MainViewController *mainViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) MainViewController *mainViewController;

-(void)saveCustomObject:(Player *)obj;
-(Player *)loadCustomObjectWithKey:(NSString*)key;


@end

そして、実装ファイルの重要な部分:

    #import "MagicApp201AppDelegate.h"
    #import "MainViewController.h"
    #import "Player.h"

    @implementation MagicApp201AppDelegate


    @synthesize window;
    @synthesize mainViewController;


    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
        //First check to see if some things exist
        int startup = [prefs integerForKey:@"appHasLaunched"];
        if (startup == nil) {
//Make the single player 
        Player *singlePlayer = [[Player alloc] init];
        NSLog([[NSString alloc] initWithFormat:@"%@\n%d\n%d",[singlePlayer name], [singlePlayer life], [singlePlayer psnCounters]]); //  test
        //Encode the single player so it can be stored in UserDefaults
        id test = [MagicApp201AppDelegate new];
        [test saveCustomObject:singlePlayer];
        [test release];
}
[prefs synchronize];
}

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
}

-(Player *)loadCustomObjectWithKey:(NSString*)key
{
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [prefs objectForKey:key ];
    Player *obj = (Player *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
    return obj;
}

Eeee、すべてのコードについて申し訳ありません。ただ助けようとしています。基本的に、アプリは起動してすぐにクラッシュします。アプリの暗号化部分に絞り込んだので、アプリがクラッシュするので、何か間違ったことをしていますが、何が原因なのかわかりません。再び助けていただければ幸いです、ありがとう!

(まだ暗号化が機能していないため、私はまだ復号化に取り掛かっていません。)


スタックトレース、またはクラッシュの原因となっている行番号など、クラッシュに関する詳細情報はありますか?私はすぐにコードに問題があるとは思わないので、開始点が役立つでしょう。
chrissr 2010

上記の例では、encodeObjectを使用して、intであるself.lifeを格納しています。代わりにencodeIntを使用してください。
broot

回答:


516

Playerクラスで、次の2つのメソッドを実装します(encodeObjectの呼び出しを独自のオブジェクトに関連するものに置き換えます)。

- (void)encodeWithCoder:(NSCoder *)encoder {
    //Encode properties, other class variables, etc
    [encoder encodeObject:self.question forKey:@"question"];
    [encoder encodeObject:self.categoryName forKey:@"category"];
    [encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if((self = [super init])) {
        //decode properties, other class vars
        self.question = [decoder decodeObjectForKey:@"question"];
        self.categoryName = [decoder decodeObjectForKey:@"category"];
        self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
    }
    return self;
}

からの読み書きNSUserDefaults

- (void)saveCustomObject:(MyObject *)object key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedObject forKey:key];
    [defaults synchronize];

}

- (MyObject *)loadCustomObjectWithKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *encodedObject = [defaults objectForKey:key];
    MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

恥知らずに借りたコード:クラスをnsuserdefaultsに保存


上記の投稿を編集して私の変更を反映させました。
イーサンミック

@chrissr NSUserDefaults defaults = [NSUserDefaults standardUserDefaults]にエラーがあります。... NSUserDefaults * defaultsである必要があります。
マギー

2
NSKeyedArchiverは揺るがしています... NSArrayまたはNSDictionaryオブジェクトに自動的に下降し、エンコード可能なカスタムオブジェクトをエンコードしているようです。
BadPirate 2012

2
私は慣れ親しんでいるわけではなく、ただ純粋な質問です。合成セッター、つまりinitメソッドでself.propertyを使用することは、Appleのガイドラインに反していませんか?
Samhan Salahuddin、2012

2
@chrissrに変更NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];してくださいNSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];。変数が一致しません。
GoRoS 2014年

36

encodeWithCoderとinitWithCoderの実装は非常に退屈なので、ライブラリRMMapper(https://github.com/roomorama/RMMapper)を作成して、カスタムオブジェクトをNSUserDefaultsに簡単かつ便利に保存できるようにします。

クラスをアーカイブ可能としてマークするには、次のように使用します。 #import "NSObject+RMArchivable.h"

カスタムオブジェクトをNSUserDefaultsに保存するには:

#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:@"SAVED_DATA"];

NSUserDefaultsからカスタムobjを取得するには:

user = [defaults rm_customObjectForKey:@"SAVED_DATA"]; 

ライブラリは、永続化するすべてのプロパティがあり、プロパティを持つすべてのプロパティを永続化することを前提としています。これが本当なら、あなたのライブラリは確かに役立つかもしれませんが、多くの場合それが役に立たないことがわかるでしょう。
エリックB

プレーンオブジェクトのみを永続化することは有用です。その使用法はJavaのGsonとかなり似ています。どのようなケースを見ていますか?
thomasdao 2013年

7
それは本当に役に立ちました。回答を読み続けてよかったです; D
Albara

カスタムオブジェクトのプロパティに他のカスタムオブジェクトがある場合はどうなりますか?
Shial 2014

@Shial:他のカスタムオブジェクトをアーカイブ可能にすると、保存されます
トーマスダオ2014

35

スウィフト4

これらの種類のタスクのすべての魔法を行うCodableプロトコルが導入されました。カスタム構造体/クラスをそれに適合させるだけです:

struct Player: Codable {
  let name: String
  let life: Double
}

また、デフォルトに格納するために、PropertyListEncoder / Decoderを使用できます。

let player = Player(name: "Jim", life: 3.14)
UserDefaults.standard.set(try! PropertyListEncoder().encode(player), forKey: kPlayerDefaultsKey)

let storedObject: Data = UserDefaults.standard.object(forKey: kPlayerDefaultsKey) as! Data
let storedPlayer: Player = try! PropertyListDecoder().decode(Player.self, from: storedObject)

このようなオブジェクトの配列やその他のコンテナクラスでも同様に機能します。

try! PropertyListDecoder().decode([Player].self, from: storedArray)

1
魅力的な作品
ネイサンバレット

Codableすべてのインスタンスメンバーが自分自身である場合にのみ無料で動作を取得することに注意してください。Codableそれ以外の場合は、エンコードコードを自分で記述する必要があります
BallpointBen

1
または、単にそれらのメンバータイプCodableも準拠させるだけです。など、再帰的に。非常にカスタムの場合にのみ、エンコードコードを記述する必要があります。
Sergiu Todirascu

Swift 4での最も簡単な方法
alstr

31

誰かが迅速なバージョンを探しているなら:

1)データのカスタムクラスを作成する

class customData: NSObject, NSCoding {
let name : String
let url : String
let desc : String

init(tuple : (String,String,String)){
    self.name = tuple.0
    self.url = tuple.1
    self.desc = tuple.2
}
func getName() -> String {
    return name
}
func getURL() -> String{
    return url
}
func getDescription() -> String {
    return desc
}
func getTuple() -> (String,String,String) {
    return (self.name,self.url,self.desc)
}

required init(coder aDecoder: NSCoder) {
    self.name = aDecoder.decodeObjectForKey("name") as! String
    self.url = aDecoder.decodeObjectForKey("url") as! String
    self.desc = aDecoder.decodeObjectForKey("desc") as! String
}

func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.name, forKey: "name")
    aCoder.encodeObject(self.url, forKey: "url")
    aCoder.encodeObject(self.desc, forKey: "desc")
} 
}

2)データを保存するには、次の関数を使用します。

func saveData()
    {
        let data  = NSKeyedArchiver.archivedDataWithRootObject(custom)
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setObject(data, forKey:"customArray" )
    }

3)取得するには:

if let data = NSUserDefaults.standardUserDefaults().objectForKey("customArray") as? NSData
        {
             custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [customData]
        }

注:ここでは、カスタムクラスオブジェクトの配列を保存および取得しています。


9

@chrissrの回答を受け取って実行すると、このコードをNSUserDefaultsカスタムカテゴリに実装して、カスタムオブジェクトを保存および取得できます。

@interface NSUserDefaults (NSUserDefaultsExtensions)

- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;

@end


@implementation NSUserDefaults (NSUserDefaultsExtensions)


- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [self setObject:encodedObject forKey:key];
    [self synchronize];

}

- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key {
    NSData *encodedObject = [self objectForKey:key];
    id<NSCoding> object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

@end

使用法:

[[NSUserDefaults standardUserDefaults] saveCustomObject:myObject key:@"myKey"];

7

スウィフト3

class MyObject: NSObject, NSCoding  {
    let name : String
    let url : String
    let desc : String

    init(tuple : (String,String,String)){
        self.name = tuple.0
        self.url = tuple.1
        self.desc = tuple.2
    }
    func getName() -> String {
        return name
    }
    func getURL() -> String{
        return url
    }
    func getDescription() -> String {
        return desc
    }
    func getTuple() -> (String, String, String) {
        return (self.name,self.url,self.desc)
    }

    required init(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
        self.url = aDecoder.decodeObject(forKey: "url") as? String ?? ""
        self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? ""
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.name, forKey: "name")
        aCoder.encode(self.url, forKey: "url")
        aCoder.encode(self.desc, forKey: "desc")
    }
    }

保存および取得するには:

func save() {
            let data  = NSKeyedArchiver.archivedData(withRootObject: object)
            UserDefaults.standard.set(data, forKey:"customData" )
        }
        func get() -> MyObject? {
            guard let data = UserDefaults.standard.object(forKey: "customData") as? Data else { return nil }
            return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject
        }

ただ、リマインダー:self内の変数の前に必須ではありませんinitencode
タマシュSengel

NSCoderを使用してオブジェクトを初期化する方法を示すことができます
Abhishek Thapliyal

@AbhishekThapliyal、私はあなたを理解していません。init(coder aDecoder:NSCoder)はそれを初期化します
Vyacheslav

6

NSUserDefaultsに保存したデータ/オブジェクトを同期します

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
    [prefs synchronize];
}

これがお役に立てば幸いです。ありがとう


Swift 3以降、「同期」を呼び出してはならないという将来の人々に注意してください。
ホセ・ラミレス

@JozemiteAppsなんで?または、このトピックの説明を含むリンクを投稿できますか?
code4latte 2017年

@ code4latte変更されたかどうかはわかりませんが、Appleのドキュメントには、「定期的な間隔で自動的に呼び出されるsynchronize()メソッドは、メモリ内キャッシュをユーザーのデフォルトデータベースと同期させる」と記載されています。以前は、即座に保存されたデータが必要であることが確かな場合にのみ呼び出す必要があると述べていました。私は、ユーザーがそれをもう呼び出すべきではないということを数回読みました。
ホセ・ラミレス2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.