Tentativa de definir um objeto que não seja de lista de propriedades como um NSUserDefaults

196

Eu pensei que sabia o que estava causando esse erro, mas não consigo entender o que fiz de errado.

Aqui está a mensagem de erro completa que estou recebendo:

Tentativa de definir um objeto de lista sem propriedade (
   "<BC_Person: 0x8f3c140>"
) como um valor NSUserDefaults para key personDataArray

Eu tenho uma Personclasse que eu acho que está em conformidade com o NSCodingprotocolo, onde eu tenho esses dois métodos na minha classe pessoal:

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

Em algum momento do aplicativo, o NSStringin BC_PersonClassestá definido e eu tenho uma DataSaveclasse que acho que está lidando com a codificação das propriedades no meu BC_PersonClass. Aqui está o código que estou usando da 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];
}

Espero que este seja o código suficiente para lhe dar uma idéia do que estou tentando fazer. Novamente, eu sei que meu problema reside em como estou codificando minhas propriedades na minha classe BC_Person, simplesmente não consigo descobrir o que está fazendo de errado.

Obrigado pela ajuda!

icekomo
fonte
Maravilha como podemos verificar se ele é lista de propriedades objeto ou não
onmyway133
that I think is conforming to the NSCoding protocolé super fácil adicionar testes de unidade para isso e realmente vale a pena.
rr1g0
O melhor a fazer é verificar seus parâmetros.Eu descobri que estava adicionando uma string que era um número.Então, isso não deve ser usado, daí o problema.
Rajal

Respostas:

272

O código que você postou tenta salvar uma matriz de objetos personalizados NSUserDefaults. Você não pode fazer isso. A implementação dos NSCodingmétodos não ajuda. Você só pode armazenar coisas como NSArray, NSDictionary, NSString, NSData, NSNumber, e NSDateno NSUserDefaults.

Você precisa converter o objeto para NSData(como em alguns códigos) e armazená-lo NSDataem NSUserDefaults. Você pode até mesmo armazenar uma NSArraydas NSData, se você precisa.

Quando você lê de volta a matriz, precisa desarquivar a NSDatapara recuperar seus BC_Personobjetos.

Talvez você queira o seguinte:

- (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
fonte
Eu tenho uma pergunta. Se eu quisesse adicionar o personEncodedObject em uma matriz e depois colocá-la no usuário Data ... eu poderia substituir: [archiveArray addObject: personEncodedObject]; com um NSArray e adicione o addObject: personEncodedObject nessa matriz e salve-o em userData? Se você seguir o que estou dizendo.
Icekomo #
Hã? Acho que você cometeu um erro de digitação, pois deseja substituir uma linha de código pela mesma linha de código. Meu código coloca a matriz de objetos codificados nos padrões do usuário.
Rddy13
Acho que estou perdido, pois pensei que você só poderia usar o NSArray com userDefaults, mas vejo no seu exemplo que você está usando o array NSMutable. Talvez eu sou apenas não entender uma coisa ...
icekomo
2
NSMutableArrayse estende NSArray. É perfeitamente bom passar um NSMutableArraypara qualquer método que espera um NSArray. Lembre-se de que a matriz realmente armazenada NSUserDefaultsserá imutável quando você a ler novamente.
Rddydy #
1
Isso custa alguma coisa no desempenho? por exemplo, se ele estava executando um loop e preenchendo um objeto de classe personalizado várias vezes, alterando-o para NSData antes de adicionar cada um a uma matriz, isso teria algum problema de desempenho maior do que apenas passar tipos de dados normais para a matriz?
Rob85
66

Parece-me um desperdício percorrer a matriz e codificar os objetos no NSData. Seu erro BC_Person is a non-property-list objectestá dizendo que a estrutura não sabe como serializar seu objeto pessoal.

Portanto, tudo o que é necessário é garantir que o seu objeto pessoal esteja em conformidade com o NSCoding. Você pode simplesmente converter sua matriz de objetos personalizados em NSData e armazenar esses padrões. Aqui está um playground:

Editar : a gravação de NSUserDefaultsé interrompida no Xcode 7, para que o playground arquive nos dados e retorne e imprima uma saída. A etapa UserDefaults é incluída caso seja corrigida posteriormente

//: 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, você armazenou uma matriz de objetos personalizados no NSUserDefaults

Daniel Galasko
fonte
Esta resposta é sólida! Eu tinha meus objetos em conformidade com o NSCoder, mas esqueci a matriz em que eles estavam armazenados. Obrigado, me salvou muitas horas!
Mikael Hellman
1
@ Daniel, acabei de colar seu código no playground xcode 7.3 e está dando um erro em "let retrievedPeople: [Person] = retrievePeople ()!" -> EXC_BAD_INSTRUCTION (código = EXC_I386_INVOP, subcódigo = 0x0). Quais ajustes eu preciso fazer? Graças
rockhammer
1
não parece @rockhammer como NSUserDefaults não funcionar em Playgrounds desde Xcode 7 :( irá atualizar em breve
Daniel Galasko
1
@ Daniel, não há problema. Fui em frente e inseri seu código no meu projeto e funciona como um encanto! Minha classe de objeto tem um NSDate além de 3 tipos de String. Eu só precisava substituir NSDate por String na função de decodificação. Graças
rockhammer
Para Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok
51

Salvar:

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

Para obter:

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

Para remover

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

Solução Swift 3

Classe de utilidade simples

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 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 ligar

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
fonte
1
Agradável! : D a melhor adaptação rápida +1
Gabo Cuadros Cardenas
developer.apple.com/documentation/foundation/userdefaults/… ... "este método é desnecessário e não deve ser usado." LOL ... alguém da apple não faz parte do time.
Nerdy Bunz
12

Swift- 4 Xcode 9.1

tente este código

você não pode armazenar o mapeador no NSUserDefault, apenas os NSData, NSString, NSNumber, NSDate, NSArray ou 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
fonte
8
Obrigado por isso. NSKeyedArchiver.archivedData (withRootObject :) foi preterido no iOS12 para um novo método que gera um erro. Talvez uma atualização para o seu código? :)
Marcy
10

Primeiro, a resposta de rmaddy (acima) é correta: implementar NSCodingnão ajuda. No entanto, você precisa implementar o NSCodinguso NSKeyedArchivere tudo mais, portanto, é apenas mais uma etapa ... a conversão viaNSData .

Métodos de exemplo

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

Assim, você pode envolver seus NSCodingobjetos em um NSArrayou NSDictionaryou qualquer ...

Dan Rosenstark
fonte
6

Eu tive esse problema ao tentar salvar um dicionário em NSUserDefaults. Acontece que ele não salvaria porque continha NSNullvalores. Então, eu apenas copiei o dicionário em um dicionário mutável, removi os valores nulos e salvei emNSUserDefaults

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

Nesse caso, eu sabia quais chaves podem ser NSNullvalores.

simple_code
fonte
4

Swift 5: O protocolo Codable pode ser usado em vez do NSKeyedArchiever .

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

A estrutura Pref é um invólucro personalizado em torno do objeto padrão 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)
            }
        }
    }
}

Então você pode usá-lo desta maneira:

Pref.user?.name = "John"

if let user = Pref.user {...
Prcela
fonte
1
if let data = UserDefaults.standard.data(forKey: keyUser) {e Btw lançando a partir Userde Useré apenas inútilreturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus
4

Rápido com@propertyWrapper

Salvar Codableobjeto emUserDefault

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

Exemplo de modelo de usuário confirma codificável

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

Como usá-lo

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

//GetValue
print(GlobalSettings.user)
dimo hamdy
fonte
Esta é a melhor RESPOSTA MODERNA. Stacker, use esta resposta se for realmente escalável.
Stefan Vasiljevic
3

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

Um objeto padrão deve ser uma lista de propriedades - ou seja, uma instância de (ou para coleções, uma combinação de instâncias de): NSData, NSString, NSNumber, NSDate, NSArray ou NSDictionary.

Se você deseja armazenar qualquer outro tipo de objeto, normalmente deve arquivá-lo para criar uma instância do NSData. Para mais detalhes, consulte o Guia de programação de preferências e configurações.

Isaak Osipovich Dunayevsky
fonte
3

Swift 5 maneira muito 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
fonte
isto é apenas para struct codificável
Kishore Kumar
Eu recebo: 'archivedData (withRootObject :)' foi preterido no macOS 10.14: Use + archivedDataWithRootObject: requireSecureCoding: error: vez
multitudes