Diego Salinas Flores
Diego Salinas Flores

Reputation: 61

Save Struct into another Struct in NSUserDefaults

I was looking for a way to save Structs in the internal storage, until I found a form in the link: https://stackoverflow.com/a/40063878/6566615, I tried it with other Structs that I have and it works correctly, at least Is a possible quick exit to my problem of storing information inside Structs, now my new problem is that the following structure (CoursesG) has parameters, one of them being another Struct. I tried to do it in a fairly structured way based on the methodology described in the link, but I throw an error when trying to save the information.


Struct CursosG & Carreras (in CursosG)

struct CursosG {
    let id: Int
    let nrc: String
    let profesor: String
    let carreras: [Carreras]
    let dia: String
    let bloque: String
    let sala: String
    let idcurso: Int
    let comentario: String
    let curso: String

    init(id: Int, nrc: String, profesor: String, carreras: [Carreras], dia: String, bloque: String, sala: String, idcurso: Int, comentario: String, curso: String) {
        self.id = id
        self.nrc = nrc
        self.profesor = profesor
        self.carreras = carreras
        self.dia = dia
        self.bloque = bloque
        self.sala = sala
        self.idcurso = idcurso
        self.comentario = comentario
        self.curso = curso
    }

    struct Carreras {
        let id: Int
        let nombre: String
        let semestre: Int

        init(id: Int, nombre: String, semestre: Int) {
            self.id = id
            self.nombre = nombre
            self.semestre = semestre
        }
    }
}

CursosG & Carreras Extensions

extension CursosG {
    init?(data: NSData) {
        if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
            id = coding.id as Int
            nrc = coding.nrc as String
            profesor = coding.profesor as String
            carreras = coding.carreras as [Carreras]
            dia = coding.dia as String
            bloque = coding.bloque as String
            sala = coding.sala as String
            idcurso = coding.idcurso as Int
            comentario = coding.comentario as String
            curso = coding.curso as String 
        } else {
            return nil
        }    
    }

    func encode() -> NSData {
        return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
    }

    private class Encoding: NSObject, NSCoding {
        let id: NSInteger
        let nrc: NSString
        let profesor: NSString
        let carreras: [Carreras]
        let dia: NSString
        let bloque: NSString
        let sala: NSString
        let idcurso: NSInteger
        let comentario: NSString
        let curso: NSString

        init(_ CursosG: CursosG) {
            id = CursosG.id as NSInteger
            nrc = CursosG.nrc as NSString
            profesor = CursosG.profesor as NSString
            carreras = CursosG.carreras as [Carreras]
            dia = CursosG.dia as NSString
            bloque = CursosG.bloque as NSString
            sala = CursosG.sala as NSString
            idcurso = CursosG.idcurso as NSInteger
            comentario = CursosG.comentario as NSString
            curso = CursosG.curso as NSString

        }

        public required init?(coder aDecoder: NSCoder) {
            if let id = aDecoder.decodeObject(forKey: "id") as? NSInteger { self.id = id } else { return nil }
            if let nrc = aDecoder.decodeObject(forKey: "nrc") as? NSString { self.nrc = nrc } else { return nil }
            if let profesor = aDecoder.decodeObject(forKey: "profesor") as? NSString { self.profesor = profesor } else { return nil }
            if let carreras = aDecoder.decodeObject(forKey: "carreras") as? [Carreras] { self.carreras = carreras } else { return nil }
            if let dia = aDecoder.decodeObject(forKey: "dia") as? NSString { self.dia = dia } else { return nil }
            if let bloque = aDecoder.decodeObject(forKey: "bloque") as? NSString { self.bloque = bloque } else { return nil }
            if let sala = aDecoder.decodeObject(forKey: "sala") as? NSString { self.sala = sala } else { return nil }
            if let idcurso = aDecoder.decodeObject(forKey: "idcurso") as? NSInteger { self.idcurso = idcurso } else { return nil }
            if let comentario = aDecoder.decodeObject(forKey: "comentario") as? NSString { self.comentario = comentario } else { return nil }
            if let curso = aDecoder.decodeObject(forKey: "curso") as? NSString { self.curso = curso } else { return nil }

        }

        public func encode(with aCoder: NSCoder) {
            aCoder.encode(id, forKey: "id")
            aCoder.encode(nrc, forKey: "nrc")
            aCoder.encode(profesor, forKey: "profesor")
            aCoder.encode(carreras, forKey: "carreras")
            aCoder.encode(dia, forKey: "dia")
            aCoder.encode(bloque, forKey: "bloque")
            aCoder.encode(sala, forKey: "sala")
            aCoder.encode(idcurso, forKey: "idcurso")
            aCoder.encode(comentario, forKey: "comentario")
            aCoder.encode(curso, forKey: "curso")
        }
    }
}

extension CursosG.Carreras {
    init?(data: NSData) {
        if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
            id = coding.id as Int
            nombre = coding.nombre as String
            semestre = coding.semestre as Int

        } else {
            return nil
        }
    }

    func encode() -> NSData {
        return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
    }

    private class Encoding: NSObject, NSCoding {
        let id: NSInteger
        let nombre: NSString
        let semestre: NSInteger

        init(_ Carreras: CursosG.Carreras) {
            id = Carreras.id as NSInteger
            nombre = Carreras.nombre as NSString
            semestre = Carreras.semestre as NSInteger

        }

        public required init?(coder aDecoder: NSCoder) {
            if let id = aDecoder.decodeObject(forKey: "id") as? NSInteger { self.id = id } else { return nil }
            if let nombre = aDecoder.decodeObject(forKey: "nombre") as? NSString { self.nombre = nombre } else { return nil }
            if let semestre = aDecoder.decodeObject(forKey: "semestre") as? NSInteger { self.semestre = semestre } else { return nil }

        }

        public func encode(with aCoder: NSCoder) {
            aCoder.encode(id, forKey: "id")
            aCoder.encode(nombre, forKey: "nombre")
            aCoder.encode(semestre, forKey: "semestre")

        }
    }
}

Save Code

let encoded = CursosAllG.map { $0.encode()}         
UserDefaults.standard.set(encoded, forKey: "my-key")

Error Code

2017-02-11 15:53:27.404 Tongoy UCN[30475:714874] -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600000486bd0

2017-02-11 15:53:27.491 Tongoy UCN[30475:714874] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600000486bd0' 

Upvotes: 0

Views: 454

Answers (1)

Ashish
Ashish

Reputation: 116

NSUserDefaults is limited in the types it can handle: NSData, NSString, NSNumber, NSDate, NSArray, and NSDictionary. Thus no Swift objects or structs can be saved. Anything else must be converted to an NSData object.

NSUserDefaults does not work the same way as NSArchiver. Since you already have added NSCoder to your classes your best choice might be to save and restore with NSArchiver to a file in the Documents directory..

From the Apple NSUserDefaults Docs:

A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

Upvotes: 1

Related Questions