Versuchen Sie, ein Nicht-Eigenschaftslistenobjekt als NSUserDefaults festzulegen

196

Ich dachte, ich wüsste, was diesen Fehler verursacht, aber ich kann nicht herausfinden, was ich falsch gemacht habe.

Hier ist die vollständige Fehlermeldung, die ich erhalte:

Versuchen Sie, ein Objekt festzulegen, das keine Eigenschaftsliste ist (
   "<BC_Person: 0x8f3c140>"
) als NSUserDefaults-Wert für den Schlüssel personDataArray

Ich habe eine PersonKlasse, die meiner Meinung nach dem NSCodingProtokoll entspricht, wobei ich beide Methoden in meiner Personenklasse habe:

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

Irgendwann in der App ist das NSStringin BC_PersonClassfestgelegt, und ich habe eine DataSaveKlasse, die meiner Meinung nach die Codierung der Eigenschaften in meinem übernimmt BC_PersonClass. Hier ist der Code, den ich aus der DataSaveKlasse verwende:

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

Ich hoffe, dies ist genug Code, um Ihnen eine Vorstellung davon zu geben, was ich versuche zu tun. Wieder weiß ich, dass mein Problem darin liegt, wie ich meine Eigenschaften in meiner BC_Person-Klasse codiere. Ich kann einfach nicht herausfinden, was ich falsch mache.

Danke für die Hilfe!

icekomo
quelle
Ich frage
that I think is conforming to the NSCoding protocolEs ist super einfach, Unit-Tests dafür hinzuzufügen, und es lohnt sich wirklich.
rr1g0
Am besten überprüfen Sie Ihre Parameter. Ich fand heraus, dass ich eine Zeichenfolge hinzufügte, die eine Zahl war. Das sollte also nicht verwendet werden, daher war das Problem.
Rajal

Antworten:

272

Der von Ihnen veröffentlichte Code versucht, ein Array von benutzerdefinierten Objekten in zu speichern NSUserDefaults. Das kannst du nicht machen. Die Implementierung der NSCodingMethoden hilft nicht. Sie können nur speichern Dinge wie NSArray, NSDictionary, NSString, NSData, NSNumber, und NSDatein NSUserDefaults.

Sie müssen das Objekt zu konvertieren NSDataund zu speichern , dass (wie Sie in einem Teil des Codes haben) NSDatain NSUserDefaults. Sie können sogar eine NSArrayvon speichern, NSDatawenn Sie müssen.

Wenn Sie das Array zurücklesen, müssen Sie die Archivierung aufheben NSData, um Ihre BC_PersonObjekte zurückzubekommen.

Vielleicht möchten Sie das:

- (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
quelle
Eine Frage habe ich. Wenn ich das personEncodedObject in ein Array einfügen und das Array dann in Benutzerdaten einfügen möchte ... könnte ich einfach Folgendes ersetzen: [archiveArray addObject: personEncodedObject]; mit einem NSArray und fügen Sie das addObject: personEncodedObject in dieses Array ein und speichern Sie es dann in userData? Wenn Sie folgen, was ich sage.
icekomo
Huh? Ich denke, Sie machen einen Tippfehler, da Sie eine Codezeile durch dieselbe Codezeile ersetzen möchten. Mein Code fügt das Array der codierten Objekte in die Benutzerstandards ein.
rmaddy
Ich glaube, ich bin verloren, weil ich dachte, Sie könnten NSArray nur mit userDefaults verwenden, aber ich sehe in Ihrem Beispiel, dass Sie ein NSMutable-Array verwenden. Vielleicht verstehe ich etwas einfach nicht ...
icekomo
2
NSMutableArrayerstreckt sich NSArray. Es ist vollkommen in Ordnung, eine NSMutableArrayan eine Methode zu übergeben, die eine erwartet NSArray. Beachten Sie, dass das tatsächlich gespeicherte Array beim NSUserDefaultsZurücklesen unveränderlich ist.
rmaddy
1
Kostet dies etwas an Leistung? Wenn dies beispielsweise eine Schleife durchlaufen und ein benutzerdefiniertes Klassenobjekt viele Male füllen und dann in NSData ändern würde, bevor jedes Objekt einem Array hinzugefügt wird, hätte dies ein größeres Leistungsproblem, als nur normale Datentypen an das Array zu übergeben?
Rob85
66

Es erscheint mir ziemlich verschwenderisch, das Array zu durchlaufen und die Objekte selbst in NSData zu kodieren. Ihr Fehler BC_Person is a non-property-list objectsagt Ihnen, dass das Framework nicht weiß, wie Sie Ihr Personenobjekt serialisieren sollen.

Sie müssen also nur sicherstellen, dass Ihr Personenobjekt der NSCoding entspricht. Anschließend können Sie einfach Ihr Array benutzerdefinierter Objekte in NSData konvertieren und diese in den Standardeinstellungen speichern. Hier ist ein Spielplatz:

Bearbeiten : Das Schreiben in NSUserDefaultsist auf Xcode 7 unterbrochen, sodass der Spielplatz in Daten archiviert und eine Ausgabe zurückgesendet und gedruckt wird. Der UserDefaults-Schritt ist enthalten, falls er zu einem späteren Zeitpunkt behoben wird

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

Und Voila, Sie haben eine Reihe von benutzerdefinierten Objekten in NSUserDefaults gespeichert

Daniel Galasko
quelle
Diese Antwort ist solide! Ich hatte meine Objekte mit NSCoder konform, aber ich habe vergessen, in welchem ​​Array sie gespeichert waren. Danke, ich habe viele Stunden gespart!
Mikael Hellman
1
@ Daniel, ich habe gerade deinen Code so eingefügt, wie er ist, in Spielplatz xcode 7.3 und es gibt einen Fehler bei "let retrievedPeople: [Person] = retrievePeople ()!" -> EXC_BAD_INSTRUCTION (Code = EXC_I386_INVOP, Subcode = 0x0). Welche Anpassungen muss ich vornehmen? Vielen Dank
Rockhammer
1
@rockhammer sieht aus wie NSUserDefaults funktioniert nicht in Playgrounds, da Xcode 7 :( in Kürze aktualisiert wird
Daniel Galasko
1
@ Daniel, kein Problem. Ich habe Ihren Code in mein Projekt eingefügt und es funktioniert wie ein Zauber! Meine Objektklasse hat zusätzlich zu 3 String-Typen ein NSDate. Ich musste nur String durch NSDate in der Dekodierungsfunktion ersetzen. Vielen Dank
Rockhammer
Für Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok
51

Speichern:

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

Bekommen:

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

Zum Entfernen

[currentDefaults removeObjectForKey:@"yourKeyName"];
jose920405
quelle
33

Swift 3-Lösung

Einfache Utility-Klasse

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

}

Modellklasse

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

    }
}

Wie rufe ich an?

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
quelle
1
Nett! : D die beste schnelle Anpassung +1
Gabo Cuadros Cardenas
developer.apple.com/documentation/foundation/userdefaults/… ... "Diese Methode ist nicht erforderlich und sollte nicht verwendet werden." LOL ... jemand bei Apple ist kein Teamplayer.
Nerdy Bunz
12

Swift-4 Xcode 9.1

Versuchen Sie diesen Code

Sie können Mapper nicht in NSUserDefault speichern, sondern nur NSData, NSString, NSNumber, NSDate, NSArray oder 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
quelle
8
Danke dafür. NSKeyedArchiver.archivedData (withRootObject :) wurde in iOS12 auf eine neue Methode verworfen, die einen Fehler auslöst. Vielleicht ein Update Ihres Codes? :)
Marcy
10

Zunächst einmal ist die Antwort von rmaddy (oben) richtig: Die Implementierung NSCodinghilft nicht. Sie müssen jedoch implementieren NSCoding, um NSKeyedArchiverund all das zu verwenden, so dass es nur noch ein Schritt ist ... Konvertieren über NSData.

Beispielmethoden

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

So können Sie Ihre NSCodingObjekte in ein NSArrayoder NSDictionaryoder was auch immer einwickeln ...

Dan Rosenstark
quelle
6

Ich hatte dieses Problem beim Speichern eines Wörterbuchs NSUserDefaults. Es stellte sich heraus, dass es nicht speichern würde, weil es NSNullWerte enthielt . Also habe ich das Wörterbuch einfach in ein veränderliches Wörterbuch kopiert, die Nullen entfernt und dann in gespeichertNSUserDefaults

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

In diesem Fall wusste ich, welche Schlüssel NSNullWerte sein könnten .

simple_code
quelle
4

Swift 5: Das codierbare Protokoll kann anstelle von NSKeyedArchiever verwendet werden .

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

Die Pref- Struktur ist ein benutzerdefinierter Wrapper um das UserDefaults-Standardobjekt.

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

Sie können es also folgendermaßen verwenden:

Pref.user?.name = "John"

if let user = Pref.user {...
Prcela
quelle
1
if let data = UserDefaults.standard.data(forKey: keyUser) {und Btw Casting von Userbis Userist nur sinnlosreturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus
4

Schnell mit@propertyWrapper

CodableObjekt speichern unterUserDefault

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

Beispiel Benutzermodell bestätigen Codierbar

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

Wie man es benutzt

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

//GetValue
print(GlobalSettings.user)
Dimo Hamdy
quelle
Dies ist die beste moderne Antwort. Stapler, verwenden Sie diese Antwort ist wirklich skalierbar.
Stefan Vasiljevic
3

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

Ein Standardobjekt muss eine Eigenschaftsliste sein, dh eine Instanz von (oder für Sammlungen eine Kombination von Instanzen von): NSData, NSString, NSNumber, NSDate, NSArray oder NSDictionary.

Wenn Sie einen anderen Objekttyp speichern möchten, sollten Sie ihn normalerweise archivieren, um eine Instanz von NSData zu erstellen. Weitere Informationen finden Sie im Programmierhandbuch für Einstellungen und Einstellungen.

Isaak Osipovich Dunayevsky
quelle
3

Swift 5 Sehr einfacher Weg

//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
quelle
Dies ist nur für codierbare Strukturen
Kishore Kumar
Ich erhalte: 'archivedData (withRootObject :)' war in macOS 10.14 veraltet: Verwenden Sie + archivedDataWithRootObject: requireSecureCoding: error: stattdessen
viele