我以为我知道是什么导致了这个错误,但我似乎不知道我做错了什么。

以下是我得到的完整错误信息:

Attempt to set a non-property-list object (
   "<BC_Person: 0x8f3c140>"
) as an NSUserDefaults value for key personDataArray

我有一个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;
}

在应用程序的某个地方,NSString在BC_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类,我似乎不能弄清楚什么,虽然我做错了。

谢谢你的帮助!


当前回答

我有这个问题,试图保存一个字典到NSUserDefaults。结果是它不能保存,因为它包含NSNull值。所以我只是复制字典到一个可变字典删除空值,然后保存到NSUserDefaults

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

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

其他回答

首先,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对象包装在NSArray或NSDictionary中。

您发布的代码尝试将一个自定义对象数组保存到NSUserDefaults中。你不能这么做。实现NSCoding方法没有帮助。你只能在NSUserDefaults中存储像NSArray, NSDictionary, NSString, NSData, NSNumber和NSDate这样的东西。

你需要将对象转换为NSData(就像你在一些代码中那样),并将NSData存储在NSUserDefaults中。如果需要,你甚至可以存储NSData的NSArray。

当你读回数组时,你需要解压缩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"];
}

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

By convention, each Cocoa and Core Foundation object listed in Table 2-1 is called a property-list object. Conceptually, you can think of “property list” as being an abstract superclass of all these classes. If you receive a property list object from some method or function, you know that it must be an instance of one of these types, but a priori you may not know which type. If a property-list object is a container (that is, an array or dictionary), all objects contained within it must also be property-list objects. If an array or dictionary contains objects that are not property-list objects, then you cannot save and restore the hierarchy of data using the various property-list methods and functions. And although NSDictionary and CFDictionary objects allow their keys to be objects of any type, if the keys are not string objects, the collections are not property-list objects.

(强调我的)

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 {...

非常简单的方法

//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"