NSUserDefaults:以可读格式存储仅具有属性列表兼容类型的结构/类



假设字典根据以下代码存储在UserDefaults中:

UserDefaults.standard.set(["name": "A preset", "value": 1], forKey: "preset")

运行此命令生成的 plist 为:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>preset</key>
    <dict>
        <key>name</key>
        <string>A preset</string>
        <key>value</key>
        <integer>1</integer>
    </dict>
</dict>
</plist>

现在,考虑此数据应由以下结构表示:

struct Preset: Codable {
    var name: String
    var value: Int
}

我想调用以下代码并获得与上述相同的结果(使用完全相同的布局存储在 plist 中的数据):

UserDefaults.standard.set(Preset(name: "A preset", value: 1), forKey: "preset")

不幸的是,这会导致错误:

Attempt to set a non-property-list object
TableViewToUserDefaults.Preset(name: "A preset", value: 1)
as an NSUserDefaults/CFPreferences value for key preset

我怎样才能实现这一点,保持相同的 plist 布局,如果可能的话,以通用的方式?(即适用于由可以在 plist 中编码的属性组成的任何结构,在这种情况下无需硬编码结构的属性(例如 namevalue

您可以制作一种面向协议的方式来解决问题。

protocol UserDefaultStorable: Codable {
  // where we store the item
  var key: String { get }
  // use to actually load/store
  func store(in userDefaults: UserDefaults) throws
  init(from userDefaults: UserDefaults) throws
}
enum LoadError: Error {
  case fail
}
// Default implementations
extension UserDefaultStorable {
  var key: String { return "key" }
  func store(in userDefaults: UserDefaults) throws {
    userDefaults.set(try JSONEncoder().encode(self), forKey: key)
  }
  init(from userDefaults: UserDefaults) throws {
    guard let data = userDefaults.data(forKey: key) else { throw LoadError.fail }
    self = try JSONDecoder().decode(Self.self, from: data)
  }
}

只需使任何Codable类型符合UserDefaultStorable即可。这种方法非常有用,因为假设您有另一个结构:

struct User: Codable {
  let name: String
  let id: Int
}

而不是在UserDefaults上定义单独的函数,你只需要这个单行:

extension User: UserDefaultStorable {}

一种解决方案是在 Struct 上编写一个函数,将其转换为字典。

struct Preset {
     var name: String
     var value: Int
     func toDictionary() -> [String:Any] {
         return ["name": self.name, "value": self.value]
     }
}

然后,要将其保存到用户默认值,您只需执行以下操作:

let p = Preset(name: "A preset", value: 1)
UserDefaults.standard.set(p.toDictionary(), forKey: "preset")

希望对您有所帮助!

UserDefaults 的以下扩展解决了这个问题,由于时间不够,我没有对其进行概括,但可能是可能的:

extension UserDefaults {
    func set(_ preset: Preset, forKey key: String) {
        set(["name": preset.name, "value": preset.value], forKey: key)
    }
}

这也适用于数组:

extension UserDefaults {
    func set(_ presets: [Preset], forKey key: String) {
        let result = presets.map { ["name":$0.name, "value":$0.value] }
        set(result, forKey: key)
    }
}

虽然UserDefaults.standard.set(:forKey:)是问题所在,但我的目标实际上是让它与可可绑定一起使用以用于NSArrayController。我决定按如下方式NSArrayController子类(请参阅哈米什对我的另一个问题的评论,这是使这个通用的拼图中最后一个缺失的部分):

extension Encodable {
    fileprivate func encode(to container: inout SingleValueEncodingContainer) throws {
        try container.encode(self)
    }
}
struct AnyEncodable: Encodable {
    var value: Encodable
    init(_ value: Encodable) {
        self.value = value
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try value.encode(to: &container)
    }
}
class NSEncodableArrayController: NSArrayController {
    override func addObject(_ object: Any) {
        let data = try! PropertyListEncoder().encode(AnyEncodable(object as! Encodable))
        let any = try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)
        super.addObject(any)
    }
}

相关内容

  • 没有找到相关文章

最新更新