如何在NSUserDefaults中存储自定义对象


268

好吧,所以我一直在闲逛,我意识到我的问题,但是我不知道如何解决。我做了一个自定义类来保存一些数据。我为此类制作对象,并且需要它们在会话之间持续。在我将所有信息放入之前NSUserDefaults,这是行不通的。

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

这是我将自定义类“ Player”放入时收到的错误消息NSUserDefaults。现在,我已经读过,显然NSUserDefaults只存储了某些类型的信息。那么我如何把我的东西放进NSUSerDefaults去呢?

我读到应该有一种方法来“编码”我的自定义对象,然后将其放入,但是我不确定如何实现它,将不胜感激!谢谢!

****编辑****

好的,所以我使用下面给出的代码(谢谢!),但是我仍然遇到一些问题。基本上,代码现在崩溃了,我不确定为什么,因为它不会给出任何错误。也许我缺少一些基本知识,但我太累了,但是我们会看到的。这是我的Custom类“ 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来存储self.life(它是一个整数)。您应该改为使用encodeInt。
broot 2011年

Answers:


516

在Player类上,实现以下两个方法(用与您自己的对象相关的东西替换对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默认值= [NSUserDefaults standardUserDefaults]中有错误;...应为NSUserDefaults *默认值。
Maggie

2
NSKeyedArchiver岩石……似乎它甚至会自动下降到NSArray或NSDictionary对象中,并对其中的任何可编码自定义对象进行编码。
BadPirate

2
我不是在脚,只是一个真正的问题,在合成方法中使用合成的二传手即self.property是否违反苹果的准则?
Samhan Salahuddin

2
@chrissr请更改NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];。变量不匹配。
GoRoS 2014年

36

我创建了一个RMMapper库(https://github.com/roomorama/RMMapper),以帮助将自定义对象保存到NSUserDefaults中变得更加容易和方便,因为实现encodeWithCoder和initWithCoder非常无聊!

要将一个类标记为可存档,只需使用: #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"]; 

您的库假定您具有要保留的所有内容的属性,并且要保留拥有属性的所有内容。如果是这样,那么您的库当然可以提供帮助,但是我想您会发现,在很多情况下不是这样。
Erik B

仅保留普通对象很有用,我想说它的用法与Java中的Gson非常相似。您正在寻找什么情况?
thomasdao

7
这真的很有帮助,很高兴我继续阅读答案; D
Albara

当我的自定义对象具有其他自定义对象的属性时该怎么办?
Shial 2014年

@Shial:如果使其他自定义对象可
归档

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
        }

提醒一下:selfinit和中的变量之前不是强制性的encode
塔玛斯·森格尔

你能告诉你如何intializing对象与NSCoder
阿布舍克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开始,您根本不应调用“同步”。
Jose Ramirez

@JozemiteApps为什么?还是可以发布带有此主题说明的链接?
code4latte

@ code4latte我不知道它是否已更改,但是Apple的文档指出:“ syncnize()方法会定期自动调用,它使内存中的缓存与用户的默认数据库保持同步。” 以前,它说只有在确定需要立即保存数据的情况下才应调用它。我已经阅读了一段时间,用户不应该再调用它了。
何塞·拉米雷斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.