我目前正在尝试将自定义Swift类保存到NSUserDefaults。以下是我的Playground的代码:
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(属性列表不能包含 “CFType”类型的对象“”
有人可以帮忙吗?我在Maverick上使用Xcode 6.0.1。感谢。
答案 0 :(得分:43)
第一个问题是你必须确保你有一个非破坏的类名:
@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)
}
答案 1 :(得分:34)
在Swift 4中,使用Codable。
在您的情况下,请使用以下代码。
class Blog : Codable {
var blogName: String?
}
现在创建它的对象。例如:
var blog = Blog()
blog.blogName = "My Blog"
现在编码如下:
if let encoded = try? JSONEncoder().encode(blog) {
UserDefaults.standard.set(encoded, forKey: "blog")
}
并像这样解码:
if let blogData = UserDefaults.standard.data(forKey: "blog"),
let blog = try? JSONDecoder().decode(Blog.self, from: blogData) {
}
答案 2 :(得分:21)
正如@ dan-beaulieu建议我回答我自己的问题:
现在是工作代码:
注意:代码在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
}
答案 3 :(得分:9)
使用 Swift 2.1 测试& Xcode 7.1.1
如果您不需要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)
}
}
测试代码可能如下所示:
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。您可以在我的回答中找到有关他们差异的更多信息here。
答案 4 :(得分:7)
在Swift 4中,您有一个替换NSCoding协议的新协议。它被称为Codable
,它支持类和Swift类型! (枚举,结构):
struct CustomStruct: Codable {
let name: String
let isActive: Bool
}
答案 5 :(得分:4)
Swift 3版本:
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")
}
}
答案 6 :(得分:4)
这是 Swift 4和5 的完整解决方案。
首先,在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
}
}
说,我们要保存和加载具有2个默认字段的自定义对象Dummy
。 Dummy
必须符合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")
答案 7 :(得分:1)
我使用自己的结构。这更容易。
<button id="getparam">Get connection param.</button>
使用:
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)
}
}
}
答案 8 :(得分:0)
我知道这个问题很旧,但是我写了一点library可能会有所帮助:
您可以通过以下方式存储对象:
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)
,然后将对象提取回其原始值:
let myStoredValue: MyObject = self.myStoredPref.get()
答案 9 :(得分:0)
您将必须像这样将历史模型类制作为NSCoding
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")
}
之后,您需要将对象归档到NSData中,然后将其保存为用户默认值,然后从用户默认值中检索它,然后再次将其取消存档。您可以像这样存档和取消存档
{{1}}
希望这可以解决您的问题
答案 10 :(得分:0)
在UserDefaults中保存自定义对象的简单示例如下:
您无需再借助THE GREAT'CODABLE'编写用于保存/检索对象的样板代码,这就是您摆脱烦人的手动编码/解码的目的 >
因此,如果您已经在使用NSCoding并切换到Codable(可编码+可分解的组合)协议,请从代码中删除以下两种方法
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
让我们从Codable的简单性开始吧...
创建要存储在UserDefaults中的自定义类或结构
struct Person : Codable {
var name:String
}
OR
class Person : Codable {
var name:String
init(name:String) {
self.name = name
}
}
按如下所示在UserDefaults中保存对象
if let encoded = try? JSONEncoder().encode(Person(name: "Dhaval")) {
UserDefaults.standard.set(encoded, forKey: "kSavedPerson")
}
从UserDefaults中加载对象,如下所示:
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)")
就这样...
保存/加载过程中很高兴..:)
答案 11 :(得分:0)
在类上实现Codable协议后,可以使用PropertyListEncoder将自定义对象保存到UserDefaults。让自定义类为User
class User: NSObject, Codable {
var id: Int?
var name: String?
var address: String?
}
Codable表示它同时实现了Decodable和Encodable协议。顾名思义,通过实现Encodable和Decodable,您可以让您的类能够在外部表示形式(例如JSON或属性列表)之间进行编码和解码。
现在,您可以将模型保存到属性列表中。
if let encodedData = try? PropertyListEncoder().encode(userModel){
UserDefaults.standard.set(encodedData, forKey:"YOUR_KEY")
UserDefaults.standard.synchronize();
}
然后您可以使用PropertyListDecoder检索模型。因为您将其编码为属性列表。
if let data = UserDefaults.standard.value(forKey:"YOUR_KEY") as? Data {
return try? PropertyListDecoder().decode(User.self, from:data)
}
答案 12 :(得分:-3)
您无法直接在对象列表中存储对象;你只能存储单独的字符串或其他原始类型(整数等)所以你需要将它存储为单独的字符串,例如:
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)
}