Sauvegarde de la classe custom Swift avec NSCoding à 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")

Lorsque j'exécute le code, j'obtiens l'erreur suivante

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

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

le même code s'écrase aussi quand 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') "

est-ce que quelqu'un peut aider? J'utilise Xcode 6.0.1 sur Maverick. Grâce.

62
demandé sur Badhan Ganesh 2014-10-20 19:33:25

8 réponses

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 {

alors vous devez encoder l'objet (dans une NSData) avant de pouvoir le stocker dans l'utilisateur par défaut:

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

de même, pour restaurer l'objet, vous aurez besoin de 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 par la main:

if let data = ud.objectForKey("blog") as? NSData {
    let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
42
répondu David Berry 2016-12-10 20:57:33

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

voici le code de travail maintenant:

Note: Il n'était pas nécessaire de démêler le nom de la classe pour que le code fonctionne dans les terrains de jeux.

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
}
19
répondu Georg 2015-10-30 16:52:05

Testé avec Swift 2.1 & Xcode 7.1.1

si vous n'avez pas besoin de blogName pour être optionnel( ce que je pense que vous n'avez pas), je recommande 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 d'essai

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, je voudrais souligner qu'il serait plus facile d'utiliser NSKeyedArchiver et enregistrer votre tableau d'objets personnalisés dans un fichier directement, au lieu de en utilisant Nsuerdefaults. Vous trouverez plus d'informations sur leurs différences dans ma réponse ici .

9
répondu Ronny Webers 2017-05-23 10:31:02

dans Swift 4, Vous avez un nouveau protocole qui remplace le protocole NSCoding. Il s'appelle Codable et il supporte les classes et les types Swift! (Enum, struct):

struct CustomStruct: Codable {
    let name: String
    let isActive: Bool
}
7
répondu Jeroen Bakker 2017-06-07 15:12:19

Dans Swift 4 , Utilisez Codable.

Dans votre cas, utilisez le code suivant.

struct Blog : NSObject, Codable {

   var blogName: String?

}

crée maintenant son objet. Par exemple:

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

encodez maintenant 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) {
}
7
répondu Ghulam Rasool 2018-03-27 19:40:50

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

}

3
répondu Nik Yekimov 2017-03-27 15:19:16

pour moi j'utilise ma structure est 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)
            NSUserDefaults.standardUserDefaults().synchronize()
        }
    }

}



Use : let userinfo = UserDefaults.UserInformation
1
répondu YannickSteph 2016-01-15 13:39:25

vous ne pouvez pas stocker un objet dans la liste de propriétés directement; vous ne pouvez stocker que des chaînes individuelles ou d'autres types primitifs (entiers, etc.) Vous devez donc le stocker sous forme de cordes individuelles, telles que:

   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
répondu Mikhail Baynov 2016-04-13 09:24:42