使用NSCoding将自定义Swift类保存到UserDefaults


79

我目前正在尝试将自定义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(属性列表不能包含类型为'CFType'的对象)”

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


这里有一个很好的教程:ios8programminginswift.wordpress.com/2014/08/17/...一个好的reddit的讨论在这里:reddit.com/r/swift/comments/28sp3z/...这里的另一个好帖子:myswiftjourney.me/2014/ 10月1日/ ......对于完整的对象存储,我建议全NSCoding -下面两个示例,从我的第二个全类结构:stackoverflow.com/questions/26233067/... stackoverfl
史蒂夫罗森伯格

Answers:


62

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

如果我想永久存储它,那么它应该如何工作。在此先感谢
Birju

1
我尝试了此操作,但是,我收到了错误消息,Type Blog不符合协议Decodable
vofili

@vofili您是否从Codable继承了Blog类?
Ghulam Rasool

如果我的Blog类中有字典[String:Any],该怎么办?
阿里·拉扎

46

第一个问题是您必须确保您拥有一个无名称的类名:

@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
谢谢。做到了。NSKeyedUnarchiver是缺少的部分。我将用一个工作示例更新我的问题。看来您可以忽略unarc.setClass,而将unarc.decodeObjectForKey下放为Blog类。
Georg

1
setClass如果您在Playground中工作,则只需要,由于各种原因,班级不会在那里自动注册。
大卫·贝里

blog上面的变量不是中的自定义对象if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) },就我而言,我需要将其if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) if let thisblog = blog as? Blog { return thisblog //this is the custom object from nsuserdefaults } }
CaffeineShots

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
}

如果我的自定义类具有UIImage或Data类型怎么办?如何在UserDefaults中存储此类?
Neelam Pursnani

12

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

假设我们要保存和加载Dummy带有2个默认字段的自定义对象。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")


这不会永久存储数据。我想在应用程序终止后存储数据。
Birju

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更容易。您可以在这里的答案中找到有关它们差异的更多信息。


仅供参考:blogName是可选的原因是该对象反映了用户拥有的实际博客。用户添加新博客的URL后,博客名称将自动从title标签派生。因此,在对象创建时没有博客名称,我想避免将新博客称为“未命名”或类似名称。
Georg

7

在Swift 4中,您有一个替代NSCoding协议的新协议。它被称为Codable并且支持类和Swift类型!(枚举,结构):

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

类实现Codable并具有可选的字符串数组:'Attempt to insert non-property list object
Vyachaslav Gerchicov

这个答案是完全错误的。Codable没有更换NSCoding。直接将对象保存到的唯一方法UserDefaults是使类NSCoding兼容。由于NSCodingclass protocol,因此无法应用于结构体/枚举类型。但是,您仍然可以使您的struct / enumCoding兼容并用于JSONSerializer对其进行编码Data,并将其存储在中UserDefaults
乔什(Josh)

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

1

我使用自己的结构。这要容易得多。

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

使用方法:

let userinfo = UserDefaults.UserInformation

1

您将必须像这样将History Model类制作为NSCoding

迅捷4和5。

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

之后,您将需要将该对象归档到NSData中,然后将其保存为用户默认值,并从用户默认值中检索它,然后再次将其取消存档。您可以像这样存档和取消存档

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

希望这能解决您的问题


1

在UserDefaults中保存自定义对象的简单示例如下:

您无需再借助THE GREAT'CODABLE'编写用于保存/检索对象的样板代码,这就是您摆脱烦人的手动编码/解码的目的。

因此,如果您已经在使用NSCoding并切换到Codable(Encodable + Decodable的组合)协议,请从代码中删除以下两种方法

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

而已。


我刚刚发现您对两个问题(这个这个问题)发表了相同的答案。如果您看到两个可以用一个答案回答的问题,请提出一个重复的标志。如果两个问题不同,请针对每个问题定制答案。谢谢!
半夜

0

我知道这个问题很旧,但是我写了一个小图书馆可能会有所帮助:

您可以通过以下方式存储对象:

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

0

在类上实现Codable协议之后,可以使用PropertyListEncoder将自定义对象保存到UserDefaults。让自定义类为User

class User: NSObject, Codable {

  var id: Int?
  var name: String?
  var address: String?
}

可编码意味着它同时实现了可解码和可编码协议。顾名思义,通过实现Encodable和Decodable,您可以使您的类具有在外部表示形式(例如JSON或Property List)之间进行编码和解码的能力。

现在,您可以将模型保存到属性列表中。

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

-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)
   }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.