Intente establecer un objeto de lista que no sea de propiedad como NSUserDefaults

196

Pensé que sabía qué estaba causando este error, pero parece que no puedo entender qué hice mal.

Aquí está el mensaje de error completo que recibo:

Intente establecer un objeto de lista que no sea de propiedad (
   "<BC_Person: 0x8f3c140>"
) como un valor NSUserDefaults para personDataArray clave

Tengo una Personclase que creo que se ajusta al NSCodingprotocolo, donde tengo ambos métodos en mi clase de persona:

- (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;
}

En algún momento de la aplicación, se establece NSStringin BC_PersonClass, y tengo una DataSaveclase que creo que está manejando la codificación de las propiedades en mi BC_PersonClass. Aquí está el código que estoy usando de la DataSaveclase:

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

Espero que sea suficiente código para darte una idea de lo que estoy tratando de hacer. Una vez más, sé que mi problema radica en cómo estoy codificando mis propiedades en mi clase BC_Person, parece que no puedo entender qué estoy haciendo mal.

¡Gracias por la ayuda!

icekomo
fuente
Me
that I think is conforming to the NSCoding protocoles muy fácil agregar pruebas unitarias para eso, y realmente vale la pena.
rr1g0
Lo mejor que puede hacer es verificar sus parámetros. Descubrí que estaba agregando una cadena que era un número. Entonces, eso no se debe usar, por lo tanto, era el problema.
Rajal

Respuestas:

272

El código que publicó intenta guardar una matriz de objetos personalizados en NSUserDefaults. No puedes hacer eso. Implementar los NSCodingmétodos no ayuda. Sólo se puede almacenar cosas como NSArray, NSDictionary, NSString, NSData, NSNumber, y NSDateen NSUserDefaults.

Necesita convertir el objeto a NSData(como lo tiene en algunos de los códigos) y almacenarlo NSDataen NSUserDefaults. Incluso puede almacenar una NSArrayde NSDatasi es necesario.

Cuando vuelva a leer la matriz, debe desarchivar el NSDatapara recuperar sus BC_Personobjetos.

Quizás quieras esto:

- (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"];
}
rmaddy
fuente
Una pregunta que tengo. Si quisiera agregar el personEncodedObject en una matriz y luego poner la matriz en los Datos del usuario ... ¿podría simplemente reemplazar: [archiveArray addObject: personEncodedObject]; con un NSArray y agregue addObject: personEncodedObject en esa matriz y luego guárdelo en userData? Si sigues lo que te digo.
icekomo
¿Eh? Creo que haces un error tipográfico ya que quieres reemplazar una línea de código con la misma línea de código. Mi código coloca la matriz de objetos codificados en los valores predeterminados del usuario.
rmaddy
Supongo que estoy perdido ya que pensé que solo podía usar NSArray con userDefaults, pero veo en su ejemplo que está usando la matriz NSMutable. Tal vez simplemente no entiendo algo ...
icekomo
2
NSMutableArrayse extiende NSArray. Está perfectamente bien pasar un NSMutableArraya cualquier método que espere un NSArray. Tenga en cuenta que la matriz realmente almacenada NSUserDefaultsserá inmutable cuando la vuelva a leer.
rmaddy el
1
¿Esto cuesta algo en rendimiento? por ejemplo, si esto se ejecutaba a través de un ciclo y llenaba un objeto de clase personalizado muchas veces y luego lo cambiaba a NSData antes de agregar cada uno a una matriz, ¿tendría esto algún problema de rendimiento mayor que simplemente pasar los tipos de datos normales a la matriz?
Rob85
66

Me parece un desperdicio ejecutar la matriz y codificar los objetos en NSData usted mismo. Su error BC_Person is a non-property-list objectle dice que el marco no sabe cómo serializar su objeto persona.

Por lo tanto, todo lo que se necesita es garantizar que su objeto personal se ajuste a NSCoding, luego simplemente puede convertir su matriz de objetos personalizados en NSData y almacenarlos en los valores predeterminados. Aquí hay un patio de recreo:

Editar : la escritura NSUserDefaultsen Xcode 7 no funciona, por lo que el área de juegos archivará los datos y volverá a imprimir una salida. El paso UserDefaults se incluye en caso de que se solucione en un punto posterior

//: 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")
}

Y Voila, ha almacenado una matriz de objetos personalizados en NSUserDefaults

Daniel Galasko
fuente
¡Esta respuesta es sólida! Tenía mis objetos conforme a NSCoder, pero olvidé la matriz en la que estaban almacenados. ¡Gracias, me ahorró muchas horas!
Mikael Hellman
1
@Daniel, acabo de pegar tu código tal como está en el patio de juegos xcode 7.3 y está dando un error en "let retrievedPeople: [Person] = retrievePeople ()!" -> EXC_BAD_INSTRUCTION (código = EXC_I386_INVOP, subcódigo = 0x0). ¿Qué ajustes necesito hacer? Gracias
rockhammer
1
@rockhammer parece que NSUserDefaults no funciona en Playgrounds ya que Xcode 7 :( se actualizará en breve
Daniel Galasko
1
@Daniel, no hay problema. ¡Seguí adelante e inserté tu código en mi proyecto y funciona de maravilla! Mi clase de objeto tiene un NSDate además de 3 tipos de cadenas. Solo necesitaba sustituir NSDate por String en la función de decodificación. Gracias
rockhammer
Para Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok
51

Ahorrar:

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

Llegar:

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

Para eliminar

[currentDefaults removeObjectForKey:@"yourKeyName"];
jose920405
fuente
34

Solución Swift 3

Clase de utilidad simple

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()
    }

}

Clase de modelo

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

    }
}

Como llamar

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!)")
}
Sazzad Hissain Khan
fuente
1
¡Agradable! : D la mejor adaptación rápida +1
Gabo Cuadros Cardenas
developer.apple.com/documentation/foundation/userdefaults/… ... "este método es innecesario y no debe usarse". LOL ... alguien en apple no es un jugador de equipo.
Nerdy Bunz
12

Swift- 4 Xcode 9.1

prueba este código

no puede almacenar el asignador en NSUserDefault, solo puede almacenar NSData, NSString, NSNumber, NSDate, NSArray o 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)
Vishal Vaghasiya
fuente
8
Gracias por esto. NSKeyedArchiver.archivedData (withRootObject :) ha quedado en desuso en iOS12 a un nuevo método que arroja un error. Tal vez una actualización de su código? :)
Marcy
10

En primer lugar, la respuesta de rmaddy (arriba) es correcta: la implementación NSCodingno ayuda. Sin embargo, debe implementar NSCodingpara usar NSKeyedArchivery todo eso, por lo que es solo un paso más ... convertir a través de NSData.

Métodos de ejemplo

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

Así se puede envolver sus NSCodingobjetos en una NSArrayo NSDictionaryo lo que sea ...

Dan Rosenstark
fuente
6

Tuve este problema al intentar guardar un diccionario en NSUserDefaults. Resulta que no se guardaría porque contenía NSNullvalores. Así que simplemente copié el diccionario en un diccionario mutable, eliminé los valores nulos y luego los guardé enNSUserDefaults

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

En este caso, sabía qué claves podrían ser NSNullvalores.

simple_code
fuente
4

Swift 5: el protocolo codificable se puede utilizar en lugar de NSKeyedArchiever .

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

La estructura Pref es un contenedor personalizado alrededor del objeto estándar 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)
            }
        }
    }
}

Entonces puedes usarlo de esta manera:

Pref.user?.name = "John"

if let user = Pref.user {...
Prcela
fuente
1
if let data = UserDefaults.standard.data(forKey: keyUser) {y por cierto, el lanzamiento de Usera no Usertiene sentido simplementereturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus el
4

Rápido con@propertyWrapper

Guardar Codableobjeto enUserDefault

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

Ejemplo de modelo de usuario confirmar Codificable

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

Cómo usarlo

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

//GetValue
print(GlobalSettings.user)
dimo hamdy
fuente
Esta es la mejor RESPUESTA MODERNA. Apilador, use esta respuesta es realmente escalable.
Stefan Vasiljevic
3

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

Un objeto predeterminado debe ser una lista de propiedades, es decir, una instancia de (o para colecciones, una combinación de instancias de): NSData, NSString, NSNumber, NSDate, NSArray o NSDictionary.

Si desea almacenar cualquier otro tipo de objeto, normalmente debe archivarlo para crear una instancia de NSData. Para obtener más detalles, consulte la Guía de programación de preferencias y configuraciones.

Isaak Osipovich Dunayevsky
fuente
3

Swift 5 Muy fácil

//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"
Shakeel Ahmed
fuente
esto es solo para estructuras codificables
Kishore Kumar
Obtengo: 'archivedData (withRootObject :)' quedó en desuso en macOS 10.14: Use + archivedDataWithRootObject: requireSecureCoding: error: en su lugar
multitudes