web-dev-qa-db-fra.com

Enregistrement de la classe Swift personnalisée avec NSCoding en UserDefaults

J'essaie actuellement de sauvegarder une classe Swift personnalisée dans NSUserDefaults. Voici le code de mon terrain de jeu:

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

Quand je lance le code, j'obtiens l'erreur suivante

L'exécution a été interrompue, raison: signal SIGABRT.

dans la dernière ligne (ud.setObject...)

Le même code se bloque également lorsque vous êtes dans une application avec le message

"Liste de propriétés non valide pour le format: 200 (les listes de propriétés ne peuvent pas contenir Objets de type 'CFType')"

Quelqu'un peut aider? J'utilise Xcode 6.0.1 sur Maverick. Merci.

63
Georg

Comme @ dan-beaulieu m'a suggéré de répondre à ma propre question:

Voici le code de travail maintenant:

Remarque: Il n'était pas nécessaire de démêler le nom de la classe pour que le code fonctionne dans 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
}
21
Georg

Le premier problème est que vous devez vous assurer que vous avez un nom de classe non mutilé:

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

Ensuite, vous devez encoder l'objet (dans un NSData) avant de pouvoir le stocker dans les paramètres par défaut de l'utilisateur:

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

De même, pour restaurer l'objet, vous devez le désarchiver:

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

Notez que si vous ne l'utilisez pas dans la cour de récréation, c'est un peu plus simple, car vous n'avez pas à inscrire la classe à la main:

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

Dans Swift 4, utilisez Codable.

Dans votre cas, utilisez le code suivant.

struct Blog : NSObject, Codable {

   var blogName: String?

}

Maintenant, créez son objet. Par exemple:

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

Maintenant, encodez-le comme ceci:

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

et le décoder comme ceci:

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

Testé avec Swift 2.1 & Xcode 7.1.1

Si vous n'avez pas besoin que blogName soit optionnel (ce que je pense que vous n'avez pas fait), je recommanderais une implémentation légèrement différente:

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

le code de test pourrait ressembler à ceci:

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

Enfin, j'aimerais souligner qu'il serait plus facile d'utiliser NSKeyedArchiver et d'enregistrer directement votre tableau d'objets personnalisés dans un fichier, au lieu d'utiliser NSUserDefaults. Vous pouvez trouver plus sur leurs différences dans ma réponse ici .

9
Ronny Webers

Dans Swift 4, vous avez un nouveau protocole qui remplace le protocole NSCoding. Il s'appelle Codable et supporte les classes et les types Swift! (Enum, structs):

struct CustomStruct: Codable {
    let name: String
    let isActive: Bool
}
7
Jeroen Bakker

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")
    }
}
4
Nik Yekimov

Voici une solution complète pour Swift 4 & 5 .

Commencez par implémenter les méthodes d'assistance dans l'extension UserDefaults:

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

Disons que nous voulons enregistrer et charger un objet personnalisé Dummy avec 2 champs par défaut. Dummy doit être conforme à 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")

1
Vadim Bulavin

J'utilise ma propre structure. C'est beaucoup plus 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)
        }
    }
}

Utiliser:

let userinfo = UserDefaults.UserInformation
1
YannickSteph

Je sais que cette question est ancienne, mais j’ai écrit un petit library qui pourrait être utile:

Vous stockez l'objet de cette façon:

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)

et ensuite extraire l'objet à sa valeur d'origine:

let myStoredValue: MyObject = self.myStoredPref.get()
0
Lena Bru

Vous devrez faire en sorte que History Model Class soit conforme à NSCoding

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

Ensuite, vous devrez archiver l'objet dans NSData, puis l'enregistrer dans les paramètres utilisateur par défaut et le récupérer à partir des paramètres utilisateur, puis le désarchiver. Vous pouvez l'archiver et le désarchiver de cette manière

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

J'espère que cela résoudra votre problème

0
Paresh Mangukiya