【问题标题】:Saving custom Swift class with NSCoding to UserDefaults使用 NSCoding 将自定义 Swift 类保存到 UserDefaults
【发布时间】:2014-12-15 15:50:39
【问题描述】:

我目前正在尝试将自定义 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。谢谢。

【问题讨论】:

标签: swift nsuserdefaults sigabrt nscoding


【解决方案1】:

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

【讨论】:

  • 如果我想永久存储它,那么它应该如何工作。提前致谢
  • 我试过了,但我收到错误消息,Type Blog 不符合协议可解码
  • @vofili 你从 Codable 继承类 Blog 吗?
  • 如果我的博客类中有字典 [String:Any] 怎么办?
【解决方案2】:

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

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

【讨论】:

  • 谢谢。做到了。 NSKeyedUnarchiver 是缺失的部分。我将使用工作示例更新我的问题。看来您可以省略 unarc.setClass 而是将 unarc.decodeObjectForKey 向下转换为 - 在这种情况下 - Blog 类。
  • 如果您在 Playground 工作,您只需要 setClass,由于各种原因,课程不会在那里自动注册。
  • 上面的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 } }
【解决方案3】:

正如@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 中存储这种类?
【解决方案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
    }
}

假设,我们要保存并加载一个自定义对象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
【解决方案5】:

使用 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 中找到更多关于它们的差异。

【讨论】:

  • 仅供参考: blogName 是可选的原因是该对象反映了用户拥有的实际博客。一旦用户添加了新博客的 URL,博客名称就会自动从标题标签派生而来。所以在对象创建时没有博客名称,我想避免将新博客称为“未命名”或类似的名称。
【解决方案6】:

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

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

【讨论】:

  • 类实现了 Codable 并且有一个可选的字符串数组:'Attempt to insert non-property list object
  • 这个答案是完全错误的。 Codable替换NSCoding。将对象直接保存到UserDefaults 的唯一方法是使类NSCoding 兼容。由于NSCodingclass protocol,它不能应用于结构/枚举类型。但是,您仍然可以使您的结构/枚举Coding 兼容并使用JSONSerializer 编码为Data,可以将其存储在UserDefaults 中。
【解决方案7】:

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

【讨论】:

    【解决方案8】:

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

    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
    

    【讨论】:

      【解决方案9】:

      你必须像这样将历史模型类制作成 NSCoding

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

      希望这能解决您的问题

      【讨论】:

        【解决方案10】:

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

        在伟大的“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 中的自定义 classstruct

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

        就是这样。

        【讨论】:

        • 我刚刚发现您针对两个问题(this onethis one)发布了相同的答案。如果您看到两个问题可以用一个答案来回答,那么请举起重复标记。如果这两个问题不同,请针对每个问题调整您的答案。谢谢!
        【解决方案11】:

        我知道这个问题很老,但我写了一点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()
        

        【讨论】:

          【解决方案12】:

          在类上实现 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)
          }
          

          【讨论】:

            【解决方案13】:

            您不能将对象直接存储在属性列表中;您只能存储单个字符串或其他原始类型(整数等)因此您需要将其存储为单个字符串,例如:

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

            【讨论】:

              猜你喜欢
              • 2020-11-08
              • 1970-01-01
              • 1970-01-01
              • 2018-05-30
              • 1970-01-01
              • 2017-05-09
              • 2019-01-12
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多