【问题标题】:How to store custom objects in NSUserDefaults如何在 NSUserDefaults 中存储自定义对象
【发布时间】:2011-01-19 22:22:23
【问题描述】:

好的,所以我一直在四处寻找,我意识到我的问题,但我不知道如何解决它。我制作了一个自定义类来保存一些数据。我为这个类制作对象,我需要它们在会话之间持续存在。在我将所有信息放入 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,对不起所有的代码。只是想帮忙。基本上,该应用程序将启动,然后立即崩溃。我已将其范围缩小到应用程序的加密部分,这就是它崩溃的地方,所以我做错了,但我不确定是什么。再次感谢您的帮助,谢谢!

(我还没有开始解密,因为我还没有开始加密工作。)

【问题讨论】:

  • 您是否有堆栈跟踪或有关崩溃的更多信息,例如导致崩溃的行号?我没有立即发现代码有任何问题,所以一个起点会很有帮助。
  • 在上面的例子中,你使用 encodeObject 来存储 self.life,它是一个 int。您应该改用 encodeInt。

标签: ios objective-c iphone encoding nsuserdefaults


【解决方案1】:

在您的 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;
}

代码厚颜无耻地借自:saving class in nsuserdefaults

【讨论】:

  • 编辑了我上面的帖子以反映我的更改。
  • @chrissr 你在 NSUserDefaults defaults = [NSUserDefaults standardUserDefaults] 中有一个错误; ... 应该是 NSUserDefaults *defaults。
  • NSKeyedArchiver 摇滚...似乎它甚至会自动下降到 NSArray 或 NSDictionary 对象并在其中编码任何可编码的自定义对象。
  • 我不是迂腐,只是一个真正的问题,在 init 方法中使用合成设置器(即 self.property)是否违反苹果指南?
  • @chrissr 请将NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object]; 更改为NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];。变量不匹配。
【解决方案2】:

Swift 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)

【讨论】:

  • 像魅力一样工作
  • 请注意,只有当所有实例成员本身都是Codable 时,您才能免费获得Codable 行为——否则,您必须自己编写一些编码代码,
  • 或者更确切地说,只是将这些成员类型也符合Codable。以此类推,递归。只有在非常自定义的情况下,您才需要编写编码代码。
  • Swift 4 最简单的方法。
【解决方案3】:

我创建了一个库 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"]; 

【讨论】:

  • 您的库假定您拥有要持久化的所有内容的属性,并且您希望持久化拥有属性的所有内容。如果这是真的,那么您的库肯定会有所帮助,但我认为您会发现在许多情况下并非如此。
  • 只保留普通对象很有用,我想说它的用法与Java中的Gson非常相似。您正在查看哪些案例?
  • 这真的很有帮助,很高兴我一直在阅读答案;D
  • 当我的自定义对象在属性中有其他自定义对象时呢?
  • @Shial:如果你让其他自定义对象存档,它也会被保存
【解决方案4】:

如果有人在寻找 swift 版本:

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]
        }

注意:这里我保存和检索自定义类对象的数组。

【讨论】:

    【解决方案5】:

    采用@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"];
    

    【讨论】:

      【解决方案6】:

      斯威夫特 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
              }
      

      【讨论】:

      • 提醒一下:selfinitencode 中的变量之前不是强制性的。
      • 你能展示一下你是如何用 NSCoder 初始化对象的吗
      • @AbhishekThapliyal,我不明白你的意思。 init(coder aDecoder: NSCoder) 初始化它
      【解决方案7】:

      将你保存的数据/对象同步到 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 我不知道它是否已更改,但 Apple 的文档指出“定期自动调用的 synchronize() 方法使内存中的缓存与用户的缓存保持同步默认数据库。”之前,它说只有在确定需要立即保存数据时才应该调用它。我已经读过几次,用户不应该再调用它了。
      猜你喜欢
      • 2017-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-28
      相关资源
      最近更新 更多