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