swift nsuserdefaults sigabrt nscoding

Guardar clases Swift personalizadas con NSCoding en UserDefaults



nsuserdefaults sigabrt (8)

Actualmente estoy intentando guardar una clase Swift personalizada en NSUserDefaults. Aquí está el código de mi área de juegos:

import Foundation class Blog : NSObject, NSCoding { var blogName: String? override init() {} required init(coder aDecoder: NSCoder) { if let blogName = aDecoder.decodeObjectForKey("blogName") as? String { self.blogName = blogName } } func encodeWithCoder(aCoder: NSCoder) { if let blogName = self.blogName { aCoder.encodeObject(blogName, forKey: "blogName") } } } var blog = Blog() blog.blogName = "My Blog" let ud = NSUserDefaults.standardUserDefaults() ud.setObject(blog, forKey: "blog")

Cuando ejecuto el código, obtengo el siguiente error

La ejecución fue interrumpida, razón: señal SIGABRT.

en la última línea ( ud.setObject ...)

El mismo código también falla cuando en una aplicación con el mensaje

"La lista de propiedades no es válida para el formato: 200 (las listas de propiedades no pueden contener objetos del tipo ''CFType'')"

¿Alguien puede ayudar? Estoy usando Xcode 6.0.1 en Maverick. Gracias.


Como @ dan-beaulieu sugirió que respondiera mi propia pregunta:

Aquí está el código de trabajo ahora:

Nota: El cambio de nombre de la clase no fue necesario para que el código funcione en Playgrounds.

import Foundation class Blog : NSObject, NSCoding { var blogName: String? override init() {} required init(coder aDecoder: NSCoder) { if let blogName = aDecoder.decodeObjectForKey("blogName") as? String { self.blogName = blogName } } func encodeWithCoder(aCoder: NSCoder) { if let blogName = self.blogName { aCoder.encodeObject(blogName, forKey: "blogName") } } } let ud = NSUserDefaults.standardUserDefaults() var blog = Blog() blog.blogName = "My Blog" ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog") if let data = ud.objectForKey("blog") as? NSData { let unarc = NSKeyedUnarchiver(forReadingWithData: data) let newBlog = unarc.decodeObjectForKey("root") as Blog }


El primer problema es que debes asegurarte de que tienes un nombre de clase no destruido:

@objc(Blog) class Blog : NSObject, NSCoding {

Luego, debe codificar el objeto (en un NSData) antes de poder almacenarlo en los valores predeterminados del usuario:

ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")

Del mismo modo, para restaurar el objeto necesitarás desarchivarlo:

if let data = ud.objectForKey("blog") as? NSData { let unarc = NSKeyedUnarchiver(forReadingWithData: data) unarc.setClass(Blog.self, forClassName: "Blog") let blog = unarc.decodeObjectForKey("root") }

Tenga en cuenta que si no lo está utilizando en el patio de recreo es un poco más simple ya que no tiene que registrar la clase a mano:

if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) }


En Swift 4 tiene un nuevo protocolo que reemplaza el protocolo NSCoding. Se llama Codable y admite clases y tipos Swift. (Enum, structs):

struct CustomStruct: Codable { let name: String let isActive: Bool }


No puede almacenar un objeto en la lista de propiedades directamente; solo puede almacenar cadenas individuales u otros tipos primitivos (enteros, etc.). Por lo tanto, debe almacenarlo como cadenas individuales, como por ejemplo:

override init() { } required public init(coder decoder: NSCoder) { func decode(obj:AnyObject) -> AnyObject? { return decoder.decodeObjectForKey(String(obj)) } self.login = decode(login) as! String self.password = decode(password) as! String self.firstname = decode(firstname) as! String self.surname = decode(surname) as! String self.icon = decode(icon) as! UIImage } public func encodeWithCoder(coder: NSCoder) { func encode(obj:AnyObject) { coder.encodeObject(obj, forKey:String(obj)) } encode(login) encode(password) encode(firstname) encode(surname) encode(icon) }


Para mí uso mi estructura es más fácil

struct UserDefaults { private static let kUserInfo = "kUserInformation" var UserInformation: DataUserInformation? { get { guard let user = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaults.kUserInfo) as? DataUserInformation else { return nil } return user } set { NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: UserDefaults.kUserInfo) NSUserDefaults.standardUserDefaults().synchronize() } } } Use : let userinfo = UserDefaults.UserInformation


Probado con Swift 2.1 y Xcode 7.1.1

Si no necesitas que blogName sea una opción (que creo que no), te recomendaría una implementación ligeramente diferente:

class Blog : NSObject, NSCoding { var blogName: String // designated initializer // // ensures you''ll never create a Blog object without giving it a name // unless you would need that for some reason? // // also : I would not override the init method of NSObject init(blogName: String) { self.blogName = blogName super.init() // call NSObject''s init method } func encodeWithCoder(aCoder: NSCoder) { aCoder.encodeObject(blogName, forKey: "blogName") } required convenience init?(coder aDecoder: NSCoder) { // decoding could fail, for example when no Blog was saved before calling decode guard let unarchivedBlogName = aDecoder.decodeObjectForKey("blogName") as? String else { // option 1 : return an default Blog self.init(blogName: "unnamed") return // option 2 : return nil, and handle the error at higher level } // convenience init must call the designated init self.init(blogName: unarchivedBlogName) } }

el código de prueba podría verse así:

let blog = Blog(blogName: "My Blog") // save let ud = NSUserDefaults.standardUserDefaults() ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog") ud.synchronize() // restore guard let decodedNSData = ud.objectForKey("blog") as? NSData, let someBlog = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? Blog else { print("Failed") return } print("loaded blog with name : /(someBlog.blogName)")

Finalmente, me gustaría señalar que sería más fácil usar NSKeyedArchiver y guardar su matriz de objetos personalizados en un archivo directamente, en lugar de usar NSUserDefaults. Puedes encontrar más sobre sus diferencias en mi respuesta here .


Swift 3 versión:

class CustomClass: NSObject, NSCoding { var name = "" var isActive = false init(name: String, isActive: Bool) { self.name = name self.isActive = isActive } // MARK: NSCoding required convenience init?(coder decoder: NSCoder) { guard let name = decoder.decodeObject(forKey: "name") as? String, let isActive = decoder.decodeObject(forKey: "isActive") as? Bool else { return nil } self.init(name: name, isActive: isActive) } func encode(with coder: NSCoder) { coder.encode(self.name, forKey: "name") coder.encode(self.isActive, forKey: "isActive") }

}


En Swift 4, use codificable.

En tu caso, usa el siguiente código.

struct Blog : NSObject, Codable { var blogName: String? }

Ahora crea su objeto. Por ejemplo:

var blog = Blog() blog.blogName = "My Blog"

Ahora codifícalo así:

if let encoded = try? JSONEncoder().encode(blog) { UserDefaults.standard.set(encoded, forKey: "blog") }

y decodifícalo así:

if let blogData = UserDefaults.standard.data(forKey: "blog"), let blog = try? JSONDecoder().decode(Blog.self, from: blogData) { }