Speichern einer benutzerdefinierten Swift-Klasse mit NSCoding in UserDefaults


79

Ich versuche derzeit, eine benutzerdefinierte Swift-Klasse in NSUserDefaults zu speichern. Hier ist der Code von meinem Spielplatz:

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ühre, wird der folgende Fehler angezeigt

Die Ausführung wurde unterbrochen, Grund: Signal SIGABRT.

in der letzten Zeile ( ud.setObject...)

Der gleiche Code stürzt auch in einer App mit der Nachricht ab

"Eigenschaftsliste für Format ungültig: 200 (Eigenschaftslisten dürfen keine Objekte vom Typ 'CFType' enthalten)"

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


Hier gibt es ein gutes Tutorial: ios8programminginswift.wordpress.com/2014/08/17/… Eine gute reddit-Diskussion hier: reddit.com/r/swift/comments/28sp3z/… Ein weiterer guter Beitrag hier: myswiftjourney.me/2014/ 10/01 /… Für die vollständige Objektspeicherung empfehle ich die vollständige NSCoding - zwei Beispiele unten, das zweite von mir mit vollständigen Klassenstrukturen: stackoverflow.com/questions/26233067/… stackoverfl
Steve Rosenberg

Antworten:


61

Verwenden Sie in Swift 4 oder höher Codable.

Verwenden Sie in Ihrem Fall den folgenden Code.

class Blog: Codable {
   var blogName: String?
}

Erstellen Sie nun das Objekt. Zum Beispiel:

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

Codiere es jetzt so:

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

und dekodiere es so:

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

Wenn ich es dauerhaft speichern möchte, wie es funktionieren soll. Vielen Dank im Voraus
Birju

1
Ich habe es versucht, aber ich erhalte die Fehlermeldung, Typ Blog entspricht nicht dem Protokoll Decodable
vofili

@vofili hast du Klasse Blog von Codable geerbt?
Ghulam Rasool

Wie mache ich das, wenn ich ein Wörterbuch [String: Any] in meiner Blog-Klasse habe?
Ali Raza

46

Das erste Problem ist, dass Sie sicherstellen müssen, dass Sie einen nicht verstümmelten Klassennamen haben:

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

Dann müssen Sie das Objekt (in eine NSData) codieren, bevor Sie es in den Benutzerstandards speichern können:

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

Um das Objekt wiederherzustellen, müssen Sie es ebenfalls entarchivieren:

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

Beachten Sie, dass es etwas einfacher ist, wenn Sie es nicht auf dem Spielplatz verwenden, da Sie die Klasse nicht manuell registrieren müssen:

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

1
Vielen Dank. Das hat es geschafft. Der NSKeyedUnarchiver war das fehlende Stück. Ich werde meine Frage mit einem funktionierenden Beispiel aktualisieren. Es scheint, dass Sie die unarc.setClass weglassen und stattdessen die unarc.decodeObjectForKey auf die Blog-Klasse übertragen können - in diesem Fall.
Georg

1
Sie brauchen das nur, setClasswenn Sie auf dem Spielplatz arbeiten. Aus verschiedenen Gründen werden Klassen dort nicht automatisch registriert.
David Berry

Die blogobige Variable ist nicht das benutzerdefinierte Objekt in if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) }. In meinem Fall muss ich sie wie folgt in mein benutzerdefiniertes Objekt if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) if let thisblog = blog as? Blog { return thisblog //this is the custom object from nsuserdefaults } }
umwandeln

21

Wie @ dan-beaulieu vorgeschlagen hat, beantworte ich meine eigene Frage:

Hier ist jetzt der Arbeitscode:

Hinweis: Das Entwirren des Klassennamens war nicht erforderlich, damit der Code auf Spielplätzen 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
}

Was ist, wenn meine benutzerdefinierte Klasse den Typ UIImage oder Data hat? Wie kann ich diese Art von Klasse in UserDefaults speichern?
Neelam Pursnani

12

Hier ist eine Komplettlösung für Swift 4 & 5 .

Implementieren Sie zunächst Hilfsmethoden in UserDefaultsErweiterung:

extension UserDefaults {

    func set<T: Encodable>(encodable: T, forKey key: String) {
        if let data = try? JSONEncoder().encode(encodable) {
            set(data, forKey: key)
        }
    }

    func value<T: Decodable>(_ type: T.Type, forKey key: String) -> T? {
        if let data = object(forKey: key) as? Data,
            let value = try? JSONDecoder().decode(type, from: data) {
            return value
        }
        return nil
    }
}

Angenommen, wir möchten ein benutzerdefiniertes Objekt Dummymit 2 Standardfeldern speichern und laden . Dummymuss entsprechen Codable:

struct Dummy: Codable {
    let value1 = "V1"
    let value2 = "V2"
}

// Save
UserDefaults.standard.set(encodable: Dummy(), forKey: "K1")

// Load
let dummy = UserDefaults.standard.value(Dummy.self, forKey: "K1")


Dadurch werden die Daten nicht dauerhaft gespeichert. Ich möchte Daten speichern, nachdem die Anwendung beendet wurde.
Birju

9

Getestet mit Swift 2.1 & Xcode 7.1.1

Wenn Sie nicht benötigen, dass blogName optional ist (was ich glaube 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)
    }
}

Testcode könnte folgendermaßen 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 wäre, NSKeyedArchiver zu verwenden und Ihr Array von benutzerdefinierten Objekten direkt in einer Datei zu speichern, anstatt NSUserDefaults zu verwenden. Mehr über ihre Unterschiede finden Sie in meiner Antwort hier .


Nur zu Ihrer Information: Der Grund dafür, dass blogName optional ist, ist, dass das Objekt die tatsächlichen Blogs eines Benutzers widerspiegelt. Der Blogname wird automatisch vom Titel-Tag abgeleitet, sobald der Benutzer eine URL für ein neues Blog hinzufügt. Zum Zeitpunkt der Objekterstellung gibt es also keinen Blognamen und ich wollte vermeiden, ein neues Blog "unbenannt" oder ähnliches zu nennen.
Georg

7

In Swift 4 haben Sie ein neues Protokoll, das das NSCoding-Protokoll ersetzt. Es heißt Codableund unterstützt Klassen und Swift-Typen! (Aufzählung, Strukturen):

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

Klasse implementiert Codable und hat eine optionale Reihe von Zeichenfolgen:'Attempt to insert non-property list object
Vyachaslav Gerchicov

Diese Antwort ist völlig falsch. Codableist nicht zu ersetzen NSCoding. Die einzige Möglichkeit, Objekte direkt zu speichern, UserDefaultsbesteht darin, die Klasse NSCodingkompatibel zu machen . Da NSCodinges sich um a handelt class protocol, kann es nicht auf Struktur- / Aufzählungstypen angewendet werden. Sie können Ihre Struktur / Aufzählung jedoch weiterhin Codingkompatibel machen und JSONSerializerzum Codieren verwenden Data, in dem sie gespeichert werden kann UserDefaults.
Josh

4

Swift 3 Version:

class CustomClass: NSObject, NSCoding {

    let name: String
    let isActive: Bool

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

1

Ich benutze meine eigene Struktur. Es ist viel einfacher.

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

Benutzen:

let userinfo = UserDefaults.UserInformation

1

Sie müssen die Verlaufsmodellklasse einfach so auf NSCoding umstellen

Swift 4 & 5.

class SSGIFHistoryModel: NSObject, NSCoding {

   var bg_image: String
   var total_like: String
   var total_view: String
   let gifID: String
   var title: String

   init(bg_image: String, total_like: String, total_view: String, gifID: String, title: String) {
    self.bg_image = bg_image
    self.total_like = total_like
    self.total_view = total_view
    self.gifID = gifID
    self.title = title

}

required init?(coder aDecoder: NSCoder) {
    self.bg_image = aDecoder.decodeObject(forKey: "bg_image") as? String ?? ""
    self.total_like = aDecoder.decodeObject(forKey: "total_like") as? String ?? ""
    self.total_view = aDecoder.decodeObject(forKey: "total_view") as? String ?? ""
    self.gifID = aDecoder.decodeObject(forKey: "gifID") as? String ?? ""
    self.title = aDecoder.decodeObject(forKey: "title") as? String ?? ""

}

func encode(with aCoder: NSCoder) {
    aCoder.encode(bg_image, forKey: "bg_image")
    aCoder.encode(total_like, forKey: "total_like")
    aCoder.encode(total_view, forKey: "total_view")
    aCoder.encode(gifID, forKey: "gifID")
    aCoder.encode(title, forKey: "title")
}
}

Danach müssen Sie das Objekt in NSData archivieren, dann in den Benutzerstandards speichern und aus den Benutzerstandards abrufen und die Archivierung wieder aufheben. Sie können es so archivieren und entarchivieren

func saveGIFHistory() {

    var GIFHistoryArray: [SSGIFHistoryModel] = []

    GIFHistoryArray.append(SSGIFHistoryModel(bg_image: imageData.bg_image, total_like: imageData.total_like, total_view: imageData.total_view, gifID: imageData.quote_id, title: imageData.title))
    if let placesData = UserDefaults.standard.object(forKey: "GIFHistory") as? NSData {
        if let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [SSGIFHistoryModel] {
            for data in placesArray {

                if data.gifID != imageData.quote_id {
                    GIFHistoryArray.append(data)
                }
            }
        }
    }

    let placesData2 = NSKeyedArchiver.archivedData(withRootObject: GIFHistoryArray)
    UserDefaults.standard.set(placesData2, forKey: "GIFHistory")
}

Hoffe das löst dein Problem


1

Ein einfaches Beispiel für das Speichern von benutzerdefinierten Objekten in UserDefaults lautet wie folgt:

Sie müssen den Boilerplate-Code zum Speichern / Abrufen von Objekten nicht mehr mit Hilfe von THE GREAT 'CODABLE' schreiben. Das ist es, was Sie vor irritierenden manuellen Codierungen / Decodierungen bewahren können.

Entfernen Sie einfach die folgenden zwei Methoden aus Ihrem Code, wenn Sie bereits NSCoding verwenden, und wechseln Sie zum Codable-Protokoll (Kombination aus Encodable + Decodable)

required init(coder decoder: NSCoder) // NOT REQUIRED ANY MORE, DECODABLE TAKES CARE OF IT

func encode(with coder: NSCoder) // NOT REQUIRED ANY MORE, ENCODABLE TAKES CARE OF IT

Beginnen wir mit der Einfachheit von Codable:

Erstellen Sie eine benutzerdefinierte Klasse oder Struktur, die Sie in UserDefaults speichern möchten

struct Person : Codable {
    var name:String
}

OR

class Person : Codable {

    var name:String

    init(name:String) {
        self.name = name
    }
}

Speichern Sie das Objekt in UserDefaults wie folgt

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

Laden Sie das Objekt aus UserDefaults wie folgt

guard let savedPersonData = UserDefaults.standard.object(forKey: "kSavedPerson") as? Data else { return }
guard let savedPerson = try? JSONDecoder().decode(Person.self, from: savedPersonData) else { return }

print("\n Saved person name : \(savedPerson.name)")

Das ist es.


Ich habe gerade festgestellt, dass Sie identische Antworten auf zwei Fragen ( diese und diese ) veröffentlicht haben. Wenn Sie zwei Fragen sehen, die mit einer Antwort beantwortet werden können, setzen Sie bitte ein doppeltes Flag. Wenn die beiden Fragen unterschiedlich sind, passen Sie Ihre Antwort bitte auf jede Frage an. Vielen Dank!
Halfer

0

Ich weiß, dass diese Frage alt ist, aber ich habe eine kleine Bibliothek geschrieben , die hilfreich sein könnte:

Sie speichern das Objekt folgendermaßen:

class MyObject: NSObject, Codable {

var name:String!
var lastName:String!

enum CodingKeys: String, CodingKey {
    case name
    case lastName = "last_name"
   }
}

self.myStoredPref = Pref<MyObject>(prefs:UserDefaults.standard,key:"MyObjectKey")
let myObject = MyObject()
//... set the object values
self.myStoredPref.set(myObject)

und dann, um das Objekt wieder auf seinen ursprünglichen Wert zu extrahieren:

let myStoredValue: MyObject = self.myStoredPref.get()

0

Sie können PropertyListEncoder verwenden, um Ihre benutzerdefinierten Objekte in UserDefaults zu speichern, nachdem Sie das Codable-Protokoll in Ihrer Klasse implementiert haben. Lassen Sie die benutzerdefinierte Klasse Benutzer sein

class User: NSObject, Codable {

  var id: Int?
  var name: String?
  var address: String?
}

Codierbar bedeutet, dass sowohl decodierbare als auch codierbare Protokolle implementiert werden. Wie der Name schon sagt, können Sie durch die Implementierung von Encodable and Decodable Ihrer Klasse die Möglichkeit geben, von und zu einer externen Darstellung, z. B. JSON oder Property List, zu codieren und zu decodieren.

Jetzt können Sie Ihr in die Eigenschaftsliste übersetztes Modell speichern.

if let encodedData = try? PropertyListEncoder().encode(userModel){
  UserDefaults.standard.set(encodedData, forKey:"YOUR_KEY")
  UserDefaults.standard.synchronize();
}

Und Sie können Ihr Modell mit PropertyListDecoder abrufen. Weil Sie es als Eigenschaftsliste codiert haben.

if let data = UserDefaults.standard.value(forKey:"YOUR_KEY") as? Data {
     return try? PropertyListDecoder().decode(User.self, from:data)
}

-3

Sie können ein Objekt nicht direkt in der Eigenschaftsliste speichern. Sie können nur einzelne Zeichenfolgen oder andere primitive Typen (Ganzzahlen usw.) speichern. Sie müssen sie daher als einzelne Zeichenfolgen speichern, z.

   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)
   }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.