2014-10-20 10 views
57

Ich versuche derzeit, eine benutzerdefinierte Swift-Klasse zu NSUserDefaults zu speichern. Hier ist der Code von meinem Spielplatz:Speichern benutzerdefinierter SWIFT-Klasse mit NSCoding zu UserDefaults

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

Wenn ich den Code ausführen, erhalte ich die Fehlermeldung „Die Ausführung unterbrochen wurde, Grund:. Signal SIGABRT“ in der letzten Zeile (ud.setObject ...)

Der gleiche Code stürzt auch in einer App mit der Meldung "Eigenschaftsliste für Format ungültig: 200 (Eigenschaftslisten können keine Objekte vom Typ 'CFType' enthalten)"

Kann jemand helfen? Ich verwende Xcode 6.0.1 auf Maverick. Vielen Dank.

+2

sollten Sie Ihren Arbeits Code als Antwort posten, erhalten Sie sich Stimmen einige auf, da die einzige Antwort, hier ist ein Link-only Antwort, die eine Nicht-Antwort von Stack-Überlauf-Richtlinien berücksichtigt wird. –

+0

Es gibt ein gutes Tutorial hier: https://ios8programminginswift.wordpress.com/2014/08/17/persisting-data-with-nsuserdefaults/ Eine gute reddit Diskussion hier: http://www.reddit.com/r/ swift/comments/28sp3z/whats_the_best_way_to_achieve_persistence_in/Noch ein guter Beitrag hier: http://www.myswiftjourney.me/2014/10/01/simple-persistent-storage-using-nsuserdefaults/ Zum vollständigen Objekt speichern empfehle ich die vollständige NSCoding - zwei Beispiel unten, das zweite von mir mit vollen Klassenstrukturen: http://stackoverflow.com/questions/26233067/simple-persistent-storage-in-swift/26233274#26233274 http: // stackoverfl –

+0

Arbeitscode unten veröffentlicht. – Georg

Antwort

16

Als @ dan-beaulieu schlug ich meine eigene Frage beantworten:

Hier ist der Arbeitscode jetzt:

Hinweis: Das Entwirren des Klassennamens war nicht notwendig, damit der Code in Playgrounds funktioniert.

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

Das erste Problem ist, müssen Sie sicherstellen, dass Sie ein nicht-verstümmelte Klassennamen haben:

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

Dann müssen Sie das Objekt kodieren (in eine NSData), bevor Sie es in die Benutzereinstellungen speichern :

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

, in ähnlicher Weise das Objekt wiederherstellen Sie es aus dem Archiv entfernen benötigen:

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

Nein te, dass, wenn Sie sie nicht auf dem Spielplatz mit ist es ein wenig einfacher, da Sie die Klasse nicht von Hand registrieren müssen:

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

Danke. Das hat es getan. Der NSKeyedUnarchiver war das fehlende Stück. Ich werde meine Frage mit einem funktionierenden Beispiel aktualisieren. Es scheint, dass Sie die unarc.setClass weglassen können und stattdessen den unarc.decodeObjectForKey - in diesem Fall - die Blog-Klasse ablehnen. – Georg

+1

Sie benötigen nur die 'setClass', wenn Sie im Playground arbeiten. Aus verschiedenen Gründen werden Klassen dort nicht automatisch registriert. –

+0

Die obige 'blog'-Variable ist nicht das benutzerdefinierte Objekt in' if last data = ud.objectForKey ("blog") als? NSData { lassen blog = NSKeyedUnarchiver.unarchiveObjectWithData (Daten) } ', in meinem Fall muss ich es auf mein benutzerdefiniertes Objekt wie diese' wenn lassen Sie Daten = ud.objectForKey ("Blog") als? NSData { lassen blog = NSKeyedUnarchiver.unarchiveObjectWithData (Daten) wenn thisblog = Blog als? Blog { return thisblog // dies ist das benutzerdefinierte Objekt von nsuserdefaults } } ' – CaffeineShots

9

Getestet mit Swift 2.1 & Xcode 7.1.1

Wenn Sie nicht benötigen einen Blogname optional zu sein (was ich denke, Sie nicht), würde ich eine etwas andere Implementierung empfehlen :

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

Test-Code könnte wie folgt aussehen:

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

Abschließend möchte ich darauf hinweisen, dass es einfacher ist, NSKeyedArchiver zu verwenden und das Array benutzerdefinierter Objekte direkt in einer Datei zu speichern, anstatt NSUserDefaults zu verwenden. Sie können mehr über ihre Unterschiede in meiner Antwort finden here.

+0

Nur zur Info: Der Grund für BlogName ist optional, dass das Objekt tatsächliche Blogs spiegelt, die ein Benutzer hat. Der Blog-Name wird automatisch vom Titel-Tag abgeleitet, sobald der Benutzer eine URL für ein neues Blog hinzufügt. Also zum Zeitpunkt der Objekterstellung gibt es keinen Blog-Namen und ich wollte vermeiden, einen neuen Blog "unbenannt" oder etwas ähnliches zu nennen. – Georg

1

Für mich ich meine Struktur einfacher ist

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

Sie nicht ein Objekt in der Eigenschaftsliste direkt speichern kann; Sie können nur einzelne Strings oder andere primitive Typen (Ganzzahlen usw.) speichern.) So müssen Sie es als einzelne Strings speichern, wie zum Beispiel:

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

Swift 3-Version:

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

}

5

In Swift 4 ein neues Protokoll haben, das die NSCoding Protokoll ersetzt . Es heißt Codable und es unterstützt Klassen und Swift-Typen! (Enum, structs):

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

Klasse implementiert Codable und verfügt über ein optionales Array von Zeichenfolgen: ''Versuch, Nicht-Eigenschaft Listenobjekt einzufügen' –

+0

Diese Antwort ist völlig falsch. "Codable" ersetzt ** nicht ** NSCoding. Die einzige Möglichkeit, Objekte direkt in 'UserDefaults' zu speichern, besteht darin, die Klasse 'NSCoding' konform zu machen. Da 'NSCoding' ein' Klassenprotokoll' ist, kann es nicht auf Struct/Enum-Typen angewendet werden. Sie können jedoch Ihre struct/enum 'Coding' konform machen und' JSONSerializer' verwenden, um in 'Data' zu codieren, was in' UserDefaults' gespeichert werden kann. – Josh