【问题标题】:Attempt to set a non-property-list object as an NSUserDefaults尝试将非属性列表对象设置为 NSUserDefaults
【发布时间】:2013-11-12 06:58:44
【问题描述】:

我以为我知道是什么导致了这个错误,但我似乎无法弄清楚我做错了什么。

这是我收到的完整错误消息:

尝试设置非属性列表对象 ( “” ) 作为关键 personDataArray 的 NSUserDefaults 值

我有一个 Person 类,我认为它符合 NSCoding 协议,我的 person 类中同时拥有这两种方法:

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.personsName forKey:@"BCPersonsName"];
    [coder encodeObject:self.personsBills forKey:@"BCPersonsBillsArray"];
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        self.personsName = [coder decodeObjectForKey:@"BCPersonsName"];
        self.personsBills = [coder decodeObjectForKey:@"BCPersonsBillsArray"];
    }
    return self;
}

在应用程序的某个时刻,BC_PersonClass 中的 NSString 已设置,并且我有一个 DataSave 类,我认为它正在处理我的 BC_PersonClass 中的属性编码。 这是我在 DataSave 类中使用的代码:

- (void)savePersonArrayData:(BC_Person *)personObject
{
   // NSLog(@"name of the person %@", personObject.personsName);

    [mutableDataArray addObject:personObject];

    // set the temp array to the mutableData array
    tempMuteArray = [NSMutableArray arrayWithArray:mutableDataArray];

    // save the person object as nsData
    NSData *personEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:personObject];

    // first add the person object to the mutable array
    [tempMuteArray addObject:personEncodedObject];

    // NSLog(@"Objects in the array %lu", (unsigned long)mutableDataArray.count);

    // now we set that data array to the mutable array for saving
    dataArray = [[NSArray alloc] initWithArray:mutableDataArray];
    //dataArray = [NSArray arrayWithArray:mutableDataArray];

    // save the object to NS User Defaults
    NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
    [userData setObject:dataArray forKey:@"personDataArray"];
    [userData synchronize];
}

我希望这些代码足以让您了解我想要做什么。 我再次知道我的问题在于我如何在我的 BC_Person 类中编码我的属性,我似乎无法弄清楚我做错了什么。

感谢您的帮助!

【问题讨论】:

  • 想知道如何检查它是否是属性列表对象
  • that I think is conforming to the NSCoding protocol 为此添加单元测试非常容易,非常值得。
  • 最好的办法是检查你的参数。我发现我正在添加一个数字字符串。所以不能使用它,因此是问题所在。

标签: ios objective-c encoding nsuserdefaults


【解决方案1】:

您发布的代码尝试将一组自定义对象保存到NSUserDefaults。你不能那样做。实现NSCoding 方法没有帮助。您只能在NSUserDefaults 中存储NSArrayNSDictionaryNSStringNSDataNSNumberNSDate 之类的内容。

您需要将对象转换为NSData(就像您在某些代码中所做的那样)并将该NSData 存储在NSUserDefaults 中。如果需要,您甚至可以存储 NSArrayNSData

当您读回数组时,您需要取消归档 NSData 以取回您的 BC_Person 对象。

也许你想要这个:

- (void)savePersonArrayData:(BC_Person *)personObject {
    [mutableDataArray addObject:personObject];

    NSMutableArray *archiveArray = [NSMutableArray arrayWithCapacity:mutableDataArray.count];
    for (BC_Person *personObject in mutableDataArray) { 
        NSData *personEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:personObject];
        [archiveArray addObject:personEncodedObject];
    }

    NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
    [userData setObject:archiveArray forKey:@"personDataArray"];
}

【讨论】:

  • 我有一个问题。如果我想将 personEncodedObject 添加到数组中,然后将数组放入用户数据中...我可以替换:[archiveArray addObject:personEncodedObject];使用 NSArray 并将 addObject:personEncodedObject 添加到该数组中,然后将其保存在 userData?如果你按照我说的去做。
  • 嗯?我认为你打错了,因为你想用同一行代码替换一行代码。我的代码确实将编码对象数组放入用户默认值中。
  • 我想我迷路了,因为我认为您只能将 NSArray 与 userDefaults 一起使用,但我在您的示例中看到您正在使用 NSMutable 数组。也许我只是不明白一些事情......
  • NSMutableArray 扩展 NSArray。将NSMutableArray 传递给任何需要NSArray 的方法都很好。请记住,实际存储在NSUserDefaults 中的数组在您读回时将是不可变的。
  • 这会影响性能吗?例如,如果这是通过循环运行并多次填充自定义类对象,然后在将每个对象添加到数组之前将其更改为 NSData,这是否会比仅将普通数据类型传递给数组有更大的性能问题?
【解决方案2】:

对我来说,自己遍历数组并将对象编码为 NSData 似乎相当浪费。您的错误 BC_Person is a non-property-list object 告诉您该框架不知道如何序列化您的 person 对象。

因此,只需确保您的 person 对象符合 NSCoding,您就可以简单地将您的自定义对象数组转换为 NSData 并将其存储为默认值。这是一个游乐场:

编辑:在 Xcode 7 上写入 NSUserDefaults 被破坏,因此 Playground 将归档到数据并返回并打印输出。包括 UserDefaults 步骤,以防它在以后修复

//: Playground - noun: a place where people can play

import Foundation

class Person: NSObject, NSCoding {
    let surname: String
    let firstname: String

    required init(firstname:String, surname:String) {
        self.firstname = firstname
        self.surname = surname
        super.init()
    }

    //MARK: - NSCoding -
    required init(coder aDecoder: NSCoder) {
        surname = aDecoder.decodeObjectForKey("surname") as! String
        firstname = aDecoder.decodeObjectForKey("firstname") as! String
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(firstname, forKey: "firstname")
        aCoder.encodeObject(surname, forKey: "surname")
    }
}

//: ### Now lets define a function to convert our array to NSData

func archivePeople(people:[Person]) -> NSData {
    let archivedObject = NSKeyedArchiver.archivedDataWithRootObject(people as NSArray)
    return archivedObject
}

//: ### Create some people

let people = [Person(firstname: "johnny", surname:"appleseed"),Person(firstname: "peter", surname: "mill")]

//: ### Archive our people to NSData

let peopleData = archivePeople(people)

if let unarchivedPeople = NSKeyedUnarchiver.unarchiveObjectWithData(peopleData) as? [Person] {
    for person in unarchivedPeople {
        print("\(person.firstname), you have been unarchived")
    }
} else {
    print("Failed to unarchive people")
}

//: ### Lets try use NSUserDefaults
let UserDefaultsPeopleKey = "peoplekey"
func savePeople(people:[Person]) {
    let archivedObject = archivePeople(people)
    let defaults = NSUserDefaults.standardUserDefaults()
    defaults.setObject(archivedObject, forKey: UserDefaultsPeopleKey)
    defaults.synchronize()
}

func retrievePeople() -> [Person]? {
    if let unarchivedObject = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaultsPeopleKey) as? NSData {
        return NSKeyedUnarchiver.unarchiveObjectWithData(unarchivedObject) as? [Person]
    }
    return nil
}

if let retrievedPeople = retrievePeople() {
    for person in retrievedPeople {
        print("\(person.firstname), you have been unarchived")
    }
} else {
    print("Writing to UserDefaults is still broken in playgrounds")
}

瞧,您已将一组自定义对象存储到 NSUserDefaults 中

【讨论】:

  • 这个答案是可靠的!我的对象符合 NSCoder,但我忘记了存储它们的数组。谢谢,为我节省了很多时间!
  • @Daniel,我刚刚将您的代码按原样粘贴到 Playground xcode 7.3 中,并且在“let retrievedPeople: [Person] = retrievePeople()!”上出现错误。 -> EXC_BAD_INSTRUCTION(代码=EXC_I386_INVOP,子代码=0x0)。我需要做哪些调整?谢谢
  • @rockhammer 看起来 NSUserDefaults 在 Playgrounds 中不起作用,因为 Xcode 7:( 将很快更新
  • @Daniel,没问题。我继续将您的代码插入到我的项目中,它就像一个魅力!我的对象类除了 3 个字符串类型之外还有一个 NSDate。我只需要在解码函数中用 NSDate 替换 String 即可。谢谢
  • 对于 Swift3:func encode(with aCoder: NSCoder)
【解决方案3】:

首先,rmaddy 的回答(上面)是正确的:实现NSCoding 没有帮助。但是,您需要实现NSCoding 才能使用NSKeyedArchiver 和所有这些,所以这只是又一步......通过NSData 转换。

示例方法

- (NSUserDefaults *) defaults {
    return [NSUserDefaults standardUserDefaults];
}

- (void) persistObj:(id)value forKey:(NSString *)key {
    [self.defaults setObject:value  forKey:key];
    [self.defaults synchronize];
}

- (void) persistObjAsData:(id)encodableObject forKey:(NSString *)key {
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:encodableObject];
    [self persistObj:data forKey:key];
}    

- (id) objectFromDataWithKey:(NSString*)key {
    NSData *data = [self.defaults objectForKey:key];
    return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}

因此,您可以将 NSCoding 对象包装在 NSArrayNSDictionary 或其他任何东西中......

【讨论】:

    【解决方案4】:

    保存:

    NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:yourObject];
    [currentDefaults setObject:data forKey:@"yourKeyName"];
    

    获取:

    NSData *data = [currentDefaults objectForKey:@"yourKeyName"];
    yourObjectType * token = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    

    删除

    [currentDefaults removeObjectForKey:@"yourKeyName"];
    

    【讨论】:

      【解决方案5】:

      我在尝试将字典保存到 NSUserDefaults 时遇到了这个问题。事实证明它不会保存,因为它包含 NSNull 值。所以我只是将字典复制到一个可变字典中删除了空值然后保存到NSUserDefaults

      NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary_trying_to_save];
      [dictionary removeObjectForKey:@"NullKey"];
      [[NSUserDefaults standardUserDefaults] setObject:dictionary forKey:@"key"];
      

      在这种情况下,我知道哪些键可能是 NSNull 值。

      【讨论】:

        【解决方案6】:

        Swift 3 解决方案

        简单实用类

        class ArchiveUtil {
        
            private static let PeopleKey = "PeopleKey"
        
            private static func archivePeople(people : [Human]) -> NSData {
        
                return NSKeyedArchiver.archivedData(withRootObject: people as NSArray) as NSData
            }
        
            static func loadPeople() -> [Human]? {
        
                if let unarchivedObject = UserDefaults.standard.object(forKey: PeopleKey) as? Data {
        
                    return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [Human]
                }
        
                return nil
            }
        
            static func savePeople(people : [Human]?) {
        
                let archivedObject = archivePeople(people: people!)
                UserDefaults.standard.set(archivedObject, forKey: PeopleKey)
                UserDefaults.standard.synchronize()
            }
        
        }
        

        模型类

        class Human: NSObject, NSCoding {
        
            var name:String?
            var age:Int?
        
            required init(n:String, a:Int) {
        
                name = n
                age = a
            }
        
        
            required init(coder aDecoder: NSCoder) {
        
                name = aDecoder.decodeObject(forKey: "name") as? String
                age = aDecoder.decodeInteger(forKey: "age")
            }
        
        
            public func encode(with aCoder: NSCoder) {
        
                aCoder.encode(name, forKey: "name")
                aCoder.encode(age, forKey: "age")
        
            }
        }
        

        如何调用

        var people = [Human]()
        
        people.append(Human(n: "Sazzad", a: 21))
        people.append(Human(n: "Hissain", a: 22))
        people.append(Human(n: "Khan", a: 23))
        
        ArchiveUtil.savePeople(people: people)
        
        let others = ArchiveUtil.loadPeople()
        
        for human in others! {
        
            print("name = \(human.name!), age = \(human.age!)")
        }
        

        【讨论】:

        【解决方案7】:

        https://developer.apple.com/reference/foundation/userdefaults

        默认对象必须是一个属性列表——即,NSData、NSString、NSNumber、NSDate、NSArray 或 NSDictionary 的实例(或集合的实例组合)。

        如果要存储任何其他类型的对象,通常应该将其归档以创建 NSData 的实例。有关详细信息,请参阅首选项和设置编程指南。

        【讨论】:

          【解决方案8】:

          Swift-4 Xcode 9.1

          试试这个代码

          不能在 NSUserDefault 中存储 mapper,只能存储 NSData、NSString、NSNumber、NSDate、NSArray 或 NSDictionary。

          let myData = NSKeyedArchiver.archivedData(withRootObject: myJson)
          UserDefaults.standard.set(myData, forKey: "userJson")
          
          let recovedUserJsonData = UserDefaults.standard.object(forKey: "userJson")
          let recovedUserJson = NSKeyedUnarchiver.unarchiveObject(with: recovedUserJsonData)
          

          【讨论】:

          • 谢谢你。 NSKeyedArchiver.archivedData(withRootObject:) 在 iOS12 中已被弃用为引发错误的新方法。也许更新您的代码? :)
          【解决方案9】:

          Swift 5非常简单的方法

          //MARK:- First you need to encoded your arr or what ever object you want to save in UserDefaults
          //in my case i want to save Picture (NMutableArray) in the User Defaults in
          //in this array some objects are UIImage & Strings
          
          //first i have to encoded the NMutableArray 
          let encodedData = NSKeyedArchiver.archivedData(withRootObject: yourArrayName)
          //MARK:- Array save in UserDefaults
          defaults.set(encodedData, forKey: "YourKeyName")
          
          //MARK:- When you want to retreive data from UserDefaults
          let decoded  = defaults.object(forKey: "YourKeyName") as! Data
          yourArrayName = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! NSMutableArray
          
          //MARK: Enjoy this arrry "yourArrayName"
          

          【讨论】:

          • 这仅适用于可编码结构
          • 我得到:'archivedData(withRootObject:)' 在 macOS 10.14 中已弃用:使用 +archivedDataWithRootObject:requiringSecureCoding:error: 代替
          • 这不起作用!收到上述警告!
          • 让我再检查一下,我会尽快更新的
          【解决方案10】:

          Swift 5:可以使用 Codable 协议代替 NSKeyedArchiever

          struct User: Codable {
              let id: String
              let mail: String
              let fullName: String
          }
          

          Pref 结构是围绕 UserDefaults 标准对象的自定义包装器。

          struct Pref {
              static let keyUser = "Pref.User"
              static var user: User? {
                  get {
                      if let data = UserDefaults.standard.object(forKey: keyUser) as? Data {
                          do {
                              return try JSONDecoder().decode(User.self, from: data)
                          } catch {
                              print("Error while decoding user data")
                          }
                      }
                      return nil
                  }
                  set {
                      if let newValue = newValue {
                          do {
                              let data = try JSONEncoder().encode(newValue)
                              UserDefaults.standard.set(data, forKey: keyUser)
                          } catch {
                              print("Error while encoding user data")
                          }
                      } else {
                          UserDefaults.standard.removeObject(forKey: keyUser)
                      }
                  }
              }
          }
          

          所以你可以这样使用它:

          Pref.user?.name = "John"
          
          if let user = Pref.user {...
          

          【讨论】:

          • if let data = UserDefaults.standard.data(forKey: keyUser) { 和顺便说一句,从UserUser 只是return try JSONDecoder().decode(User.self, from: data) 没有意义
          【解决方案11】:

          Swift@propertyWrapper

          Codable对象保存到UserDefault

          @propertyWrapper
              struct UserDefault<T: Codable> {
                  let key: String
                  let defaultValue: T
          
                  init(_ key: String, defaultValue: T) {
                      self.key = key
                      self.defaultValue = defaultValue
                  }
          
                  var wrappedValue: T {
                      get {
          
                          if let data = UserDefaults.standard.object(forKey: key) as? Data,
                              let user = try? JSONDecoder().decode(T.self, from: data) {
                              return user
          
                          }
          
                          return  defaultValue
                      }
                      set {
                          if let encoded = try? JSONEncoder().encode(newValue) {
                              UserDefaults.standard.set(encoded, forKey: key)
                          }
                      }
                  }
              }
          
          
          
          
          enum GlobalSettings {
          
              @UserDefault("user", defaultValue: User(name:"",pass:"")) static var user: User
          }
          

          示例用户模型确认可编码

          struct User:Codable {
              let name:String
              let pass:String
          }
          

          如何使用

          //Set value 
           GlobalSettings.user = User(name: "Ahmed", pass: "Ahmed")
          
          //GetValue
          print(GlobalSettings.user)
          

          【讨论】:

          • 这是最好的现代答案。 Stacker,使用这个答案是真正可扩展的。
          【解决方案12】:

          我遇到了这个问题,最终发现这是因为我试图使用NSNumber 作为字典键,而属性列表只允许字符串作为键。 setObject:forKey: 的文档没有提到这个限制,但它链接到的 About Property Lists 页面确实:

          按照惯例,表 2-1 中列出的每个 Cocoa 和 Core Foundation 对象都称为属性列表对象。从概念上讲,您可以将“属性列表”视为所有这些类的抽象超类。如果您从某个方法或函数接收到一个属性列表对象,您就知道它必须是这些类型之一的实例,但您可能不知道先验是哪种类型。如果属性列表对象是容器(即数组或字典),则其中包含的所有对象也必须是属性列表对象。如果数组或字典包含不是属性列表对象的对象,则无法使用各种属性列表方法和函数保存和恢复数据层次结构。尽管 NSDictionary 和 CFDictionary 对象允许它们的键是任何类型的对象,如果键不是字符串对象,则集合不是属性列表对象。

          (强调我的)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-06-19
            • 1970-01-01
            • 2019-08-23
            • 2014-06-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多