Salvataggio della class Swift personalizzata con NSCoding a UserDefaults

Attualmente sto cercando di salvare una class Swift personalizzata in NSUserDefaults. Ecco il codice dal mio Playground:

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

Quando eseguo il codice, ottengo il seguente errore

L’esecuzione è stata interrotta, motivo: segnale SIGABRT.

nell’ultima riga ( ud.setObject …)

Lo stesso codice si blocca anche quando si trova in un’app con il messaggio

    “Elenco proprietà non valido per il formato: 200 (gli elenchi di proprietà non possono contenere oggetti di tipo” CFType “)”

    Qualcuno può aiutare? Sto usando Xcode 6.0.1 su Maverick. Grazie.

    Il primo problema è che devi assicurarti di avere un nome di class non mutilato:

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

    Quindi devi codificare l’object (in un NSData) prima di poterlo memorizzare nelle impostazioni predefinite dell’utente:

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

    Allo stesso modo, per ripristinare l’object è necessario annullarlo:

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

    Nota che se non lo stai usando nel parco giochi è un po ‘più semplice dato che non devi registrare la lezione a mano:

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

    Come @ dan-beaulieu mi ha suggerito di rispondere alla mia stessa domanda:

    Ecco il codice di lavoro ora:

    Nota: non è stato necessario eseguire la demangling del nome della class affinché il codice funzioni in 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 } 

    Testato con Swift 2.1 e Xcode 7.1.1

    Se non hai bisogno che blogName sia facoltativo (cosa che penso che tu non sia), ti consiglierei un’implementazione leggermente diversa:

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

    codice di prova potrebbe assomigliare a questo:

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

    Infine, vorrei sottolineare che sarebbe più semplice utilizzare NSKeyedArchiver e salvare direttamente la serie di oggetti personalizzati su un file, invece di utilizzare NSUserDefaults. Puoi trovare ulteriori informazioni sulle loro differenze nella mia risposta qui .

    In Swift 4 hai un nuovo protocollo che sostituisce il protocollo NSCoding. Si chiama Codable e supporta classi e tipi Swift! (Enum, structs):

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

    In Swift 4, Usa codifica.

    Nel tuo caso, usa il seguente codice.

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

    Ora crea il suo object. Per esempio:

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

    Ora codificalo in questo modo:

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

    e decodificarlo in questo modo:

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

    Versione Swift 3:

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

    }

    Per me io uso la mia struttura è più facile

     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 

    Non è ansible memorizzare direttamente un object nell’elenco delle proprietà; è ansible memorizzare solo stringhe individuali o altri tipi primitivi (numeri interi, ecc.) Quindi è necessario archiviarli come singole stringhe, ad esempio:

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