尝试将非属性列表对象设置为NSUserDefaults


196

我以为我知道导致此错误的原因,但是我似乎无法弄清楚自己做错了什么。

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

尝试设置非属性列表对象(
   “ <BC_Person:0x8f3c140>”
)作为keyUserDataArray的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;
}

在应用中的某一时刻,NSStringBC_PersonClass设置,我有一个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类中编码属性,尽管我做错了,但我似乎无法弄清楚是什么。

谢谢您的帮助!


想知道我们如何检查它是否是属性列表对象
onmyway133 '16

that I think is conforming to the NSCoding protocol为此非常容易添加单元测试,这确实值得。
rr1g0

最好的办法是检查您的参数。我发现我要添加一个字符串,它是一个数字,所以不可以使用,因此就是问题所在。
拉贾尔

Answers:


272

您发布的代码试图将一组自定义对象保存到NSUserDefaults。你不能那样做。实现NSCoding方法无济于事。只能存储之类的东西NSArrayNSDictionaryNSStringNSDataNSNumber,和NSDateNSUserDefaults

您需要将对象转换为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并将ad​​dObject:personEncodedObject添加到该数组中,然后将其保存在userData中?如果您遵循我的意思。
icekomo 2013年

??我想您输入错误,因为您想用同一行代码替换一行代码。我的代码确实将编码对象数组放入用户默认值中。
rmaddy13年

我想我迷路了,因为我以为您只能将NSArray与userDefaults一起使用,但是我在您的示例中看到您正在使用NSMutable数组。也许我只是不了解某件事...
icekomo

2
NSMutableArray延伸NSArray。最好将传递NSMutableArray给需要的任何方法NSArray。请记住,实际存储在其中的数组NSUserDefaults在您读回时将是不可变的。
rmaddy13年

1
这会在性能上付出任何代价吗?例如,如果这遍历一个循环并多次填充一个自定义类对象,然后将其更改为NSData,然后再将每个对象添加到数组中,这是否会比将普通数据类型传递给数组更大的性能问题?
Rob85

66

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

因此,所需要做的就是确保您的人员对象符合NSCoding,然后您只需将自定义对象数组转换为NSData并将其存储为默认值即可。这是一个游乐场:

编辑NSUserDefaults在Xcode 7上写入中断,因此游乐场将存档到数据并返回并打印输出。包括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,但是我忘记了它们存储在其中的数组。谢谢,为我节省了很多时间!
Mikael Hellman 2015年

1
@Daniel,我只是将您的代码原样粘贴到了运动场xcode 7.3中,它给出了一个错误,“让letteredPeople:[Person] = resolvePeople()!” -> EXC_BAD_INSTRUCTION(代码= EXC_I386_INVOP,子代码= 0x0)。我需要进行哪些调整?谢谢
rockhammer '16

1
@rockhammer看起来NSUserDefaults在Playgrounds中不起作用,因为Xcode 7 :(将很快更新
Daniel Galasko,2016年

1
@Daniel,没问题。我继续将您的代码插入到我的项目中,它的工作就像一个魅力!我的对象类除3个String类型外还有一个NSDate。我只需要在解码功能中用NSDate代替String。谢谢
rockhammer '16

对于Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok

51

保存:

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"];

34

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!)")
}

1
真好!:D最快的改编+1
Gabo Cuadros Cardenas

developer.apple.com/documentation/foundation/userdefaults / ... ...“此方法是不必要的,不应使用。” 大声笑...苹果公司的人不是团队合作者。
Nerdy Bunz

12

迅捷-4 Xcode 9.1

试试这个代码

您不能在NSUserDefault中存储映射器,只能存储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)

8
这次真是万分感谢。NSKeyedArchiver.archivedData(withRootObject :)在iOS12中已被弃用,引发了错误的新方法。也许是您代码的更新?:)
Marcy

10

首先,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或其他任何东西中...


6

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

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

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


4

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

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

结构是围绕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 {...

1
if let data = UserDefaults.standard.data(forKey: keyUser) {并且顺便说一句,从铸造UserUser是没有意义的只是return try JSONDecoder().decode(User.self, from: data)
狮子座Dabus

4

迅捷@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)

这是最好的现代答案。堆叠器,使用此答案是真正可缩放的。
Stefan Vasiljevic


3

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"

这仅适用于可编码结构
Kishore Kumar

我得到:在macOS 10.14中不推荐使用“ archivedData(withRootObject :)”:使用+ archivedDataWithRootObject:requiringSecureCoding:error:相反
多个
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.