将具有NSCoding的自定义SWIFT类保存到UserDefaults

我目前正试图保存一个自定义的Swift类到NSUserDefaults。 这里是我的游乐场的代码:

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

当我运行代码时,出现错误“执行被中断,原因:信号SIGABRT”。 在最后一行(ud.setObject …)

相同的代码也崩溃时,在应用程序中的消息“属性列表无效的格式:200(属性列表不能包含CFTypetypes的对象)”

任何人都可以帮忙吗? 我在Maverick上使用Xcode 6.0.1。 谢谢。

第一个问题是你必须确保你有一个没有损坏的类名:

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

然后,你必须编码的对象(到一个NSData),然后才能将其存储到用户的默认值:

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

同样,要恢复对象,您需要将其取消归档:

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

请注意,如果您不在操场上使用它,则会更简单一些,因为您无需手动注册课程:

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

正如@ dan-beaulieubuild议我回答我自己的问题:

这是现在的工作代码:

注意:对于代码在Playgrounds中工作,不需要对类名进行Demangling。

 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 } 

使用Swift 2.1和Xcode 7.1.1进行testing

如果你不需要blogName是可选的(我认为你不需要),我会推荐一个稍微不同的实现:

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

testing代码可能如下所示:

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

最后,我想指出的是,使用NSKeyedArchiver并将自定义对象的数组直接保存到一个文件将会更容易,而不是使用NSUserDefaults。 你可以在这里find更多关于他们的差异。

在Swift 4中,你有一个替代NSCoding协议的新协议。 它被称为Codable ,它支持类和Swifttypes! (Enum,structs):

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

Swift 3版本:

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

}

对我来说,我使用我的结构更容易

 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 

您不能直接在属性列表中存储对象; 您只能存储单个string或其他基本types(整数等)。因此,您需要将其存储为单个string,例如:

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