Tentativo di impostare un oggetto elenco non di proprietà come NSUserDefaults


196

Pensavo di sapere cosa stava causando questo errore, ma non riesco a capire cosa ho fatto di sbagliato.

Ecco il messaggio di errore completo che sto ricevendo:

Tentativo di impostare un oggetto elenco non di proprietà (
   "<BC_Person: 0x8f3c140>"
) come valore NSUserDefaults per key personDataArray

Ho una Personclasse che penso sia conforme al NSCodingprotocollo, in cui ho entrambi questi metodi nella mia classe personale:

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

Ad un certo punto nell'app, il NSStringin BC_PersonClassè impostato e ho una DataSaveclasse che penso stia gestendo la codifica delle proprietà nella mia BC_PersonClass. Ecco il codice che sto usando dalla DataSaveclasse:

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

Spero che questo sia abbastanza codice per darti un'idea di ciò che sto cercando di fare. Ancora una volta so che il mio problema risiede nel modo in cui sto codificando le mie proprietà nella mia classe BC_Person, non riesco proprio a capire cosa, anche se sto sbagliando.

Grazie per l'aiuto!


Ci chiediamo come possiamo verificare se si tratta di un oggetto dell'elenco delle proprietà oppure no
onmyway133,

that I think is conforming to the NSCoding protocolè semplicissimo aggiungere test unitari per questo, e ne vale davvero la pena.
rr1g0,

La cosa migliore da fare è controllare i tuoi parametri. Ho scoperto che stavo aggiungendo una stringa che era un numero, quindi non doveva essere usato, quindi il problema era.
Rajal,

Risposte:


272

Il codice che hai pubblicato tenta di salvare un array di oggetti personalizzati NSUserDefaults. Non puoi farlo. L'implementazione dei NSCodingmetodi non aiuta. È possibile memorizzare solo le cose come NSArray, NSDictionary, NSString, NSData, NSNumber, e NSDatein NSUserDefaults.

Devi convertire l'oggetto in NSData(come in alcuni dei codici) e archiviarlo NSDatain NSUserDefaults. È anche possibile memorizzare un NSArraydi NSDatase necessario.

Quando rileggi l'array devi annullare l'archiviazione NSDataper recuperare i tuoi BC_Personoggetti.

Forse vuoi questo:

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

Una domanda che ho Se volessi aggiungere personEncodedObject in un array e quindi inserire l'array in User Data ... potrei semplicemente sostituire: [archiveArray addObject: personEncodedObject]; con un NSArray e aggiungere addObject: personEncodedObject in quell'array e salvarlo in userData? Se segui quello che sto dicendo.
Icekomo,

Eh? Penso che tu faccia un refuso poiché vuoi sostituire una riga di codice con la stessa riga di codice. Il mio codice inserisce la matrice di oggetti codificati nelle impostazioni predefinite dell'utente.
rmaddy,

Immagino di essermi perso poiché pensavo che potessi usare NSArray solo con userDefaults, ma vedo nel tuo esempio che stai usando l'array NSMutable. Forse non capisco qualcosa ...
Icekomo,

2
NSMutableArraysi estende NSArray. Va perfettamente bene passare NSMutableArraya qualsiasi metodo che si aspetta un NSArray. Tieni presente che l'array effettivamente archiviato NSUserDefaultssarà immutabile quando lo rileggi.
rmaddy

1
Questo costa qualcosa in termini di prestazioni? ad esempio se questo fosse in esecuzione attraverso un ciclo e popolasse più volte un oggetto di classe personalizzato, quindi lo cambiasse in NSData prima di aggiungerne uno a un array, ciò avrebbe qualche problema di prestazioni maggiore rispetto al semplice passaggio di tipi di dati normali all'array?
Rob85,

66

Mi sembra piuttosto inutile scorrere l'array e codificare gli oggetti in NSData. Il tuo errore BC_Person is a non-property-list objectti dice che il framework non sa come serializzare il tuo oggetto persona.

Quindi tutto ciò che serve è assicurarsi che il tuo oggetto personale sia conforme a NSCoding, quindi puoi semplicemente convertire il tuo array di oggetti personalizzati in NSData e archiviarlo in valori predefiniti. Ecco un parco giochi:

Modifica : la scrittura su NSUserDefaultsè interrotta su Xcode 7, quindi il parco giochi archivia i dati e torna indietro e stampa un output. Il passaggio UserDefaults è incluso nel caso in cui venga risolto in un momento successivo

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

E Voila, hai archiviato una serie di oggetti personalizzati in NSUserDefaults


Questa risposta è solida! Avevo i miei oggetti conformi a NSCoder, ma mi ero dimenticato dell'array in cui erano memorizzati. Grazie, mi hai risparmiato molte ore!
Mikael Hellman,

1
@Daniel, ho appena incollato il tuo codice così come è nel parco giochi xcode 7.3 e sta dando un errore su "let retrievedPeople: [Person] = retrievePeople ()!" -> EXC_BAD_INSTRUCTION (codice = EXC_I386_INVOP, sottocodice = 0x0). Quali aggiustamenti devo fare? Grazie
rockhammer il

1
@rockhammer sembra che NSUserDefaults non funzioni in Playgrounds poiché Xcode 7 :( verrà aggiornato a breve
Daniel Galasko,

1
@ Daniele, nessun problema. Sono andato avanti e ho inserito il tuo codice nel mio progetto e funziona come un incantesimo! La mia classe di oggetti ha un NSDate oltre a 3 tipi di stringhe. Avevo solo bisogno di sostituire NSDate per String nella funzione di decodifica. Grazie
rockhammer il

Per Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok,

51

Salvare:

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

Ottenere:

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

Per rimuovere

[currentDefaults removeObjectForKey:@"yourKeyName"];

34

Soluzione Swift 3

Semplice classe di utilità

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

}

Classe del modello

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

    }
}

Come chiamare

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
Bello! : D il miglior adattamento rapido +1
Gabo Cuadros Cardenas il

developer.apple.com/documentation/foundation/userdefaults/… ... "questo metodo non è necessario e non deve essere utilizzato." LOL ... qualcuno di Apple non è un giocatore di squadra.
Nerdy Bunz,

12

Swift- 4 Xcode 9.1

prova questo codice

non è possibile archiviare il mapper in NSUserDefault, è possibile archiviare solo 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)

8
Grazie per questo. NSKeyedArchiver.archivedData (conRootObject :) è stato deprecato in iOS12 con un nuovo metodo che genera un errore. Forse un aggiornamento al tuo codice? :)
Marcy,

10

Prima di tutto, la risposta di rmaddy (sopra) è corretta: l'implementazione NSCodingnon aiuta. Tuttavia, è necessario implementare NSCodingper utilizzare NSKeyedArchivere tutto il resto, quindi è solo un altro passo ... la conversione tramite NSData.

Metodi di esempio

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

Così si può avvolgere le NSCodingoggetti in una NSArrayo NSDictionaryo qualsiasi altra cosa ...


6

Ho avuto questo problema nel tentativo di salvare un dizionario in NSUserDefaults. Si scopre che non salverebbe perché conteneva NSNullvalori. Quindi ho appena copiato il dizionario in un dizionario modificabile rimosso i null quindi salvatoNSUserDefaults

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

In questo caso sapevo quali chiavi potevano essere NSNullvalori.


4

Swift 5: è possibile utilizzare il protocollo Codable anziché NSKeyedArchiever .

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

Il Pref struct è personalizzato wrapper per l'oggetto standard 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)
            }
        }
    }
}

Quindi puoi usarlo in questo modo:

Pref.user?.name = "John"

if let user = Pref.user {...

1
if let data = UserDefaults.standard.data(forKey: keyUser) {e Btw lanciare da Usera Userè inutilereturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus,

4

Rapido con@propertyWrapper

Salva Codableoggetto inUserDefault

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

Esempio Conferma modello utente Codificabile

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

Come usarlo

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

//GetValue
print(GlobalSettings.user)

Questa è la migliore RISPOSTA MODERNA. Stacker, utilizzare questa risposta è veramente scalabile.
Stefan Vasiljevic,

3

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

Un oggetto predefinito deve essere un elenco di proprietà, ovvero un'istanza di (o per le raccolte, una combinazione di istanze di): NSData, NSString, NSNumber, NSDate, NSArray o NSDictionary.

Se si desidera archiviare qualsiasi altro tipo di oggetto, in genere è necessario archiviarlo per creare un'istanza di NSData. Per ulteriori dettagli, consultare la Guida alla programmazione delle preferenze e delle impostazioni.


3

Swift 5 Modo molto semplice

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

questo è solo per la struttura codificabile
Kishore Kumar il

Ottengo: 'archivedData (withRootObject :)' è stato deprecato in macOS 10.14: Usa + archivedDataWithRootObject: richiedendoSecureCoding: errore: invece
moltitudini
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.