NSUserDefaults:以可读格式存储仅具有属性列表兼容类型的结构/类 [英] NSUserDefaults: storing struct/class with only property list compliant types, in a readable format

查看:106
本文介绍了NSUserDefaults:以可读格式存储仅具有属性列表兼容类型的结构/类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Suppose a dictionary is stored in UserDefaults according to the following code:

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

运行此命令所产生的plist是:

The plist that results from running this command is:

<?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>

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

Now, consider this data should be represented by the following struct:

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

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

I'd like to call the following code and get the same results as above (data stored in the plist using exactly the same layout):

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

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

Unfortunately this results in an error:

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)

How can I achieve this, keeping the same plist layout, and if possible in a generic way? (i.e. one which works for any struct consisting of properties that can be encoded in a plist, without hardcoding the struct's properties such as name and value in this case)

推荐答案

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

The following extension to UserDefaults solves the problem, and I didn't generalize it for lack of time, but it may be possible:

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

这也可以在数组上工作:

This can work on arrays as well:

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子类化(请参见Hamish对我的另一个问题,这是使这个通用名称遗忘的最后一个难题):

While UserDefaults.standard.set(:forKey:) is what the question was about, my goal was actually to get it working with Cocoa bindings for use with NSArrayController. I decided to subclass NSArrayController as follows (see comment by Hamish to my other question, which was the last missing piece of the puzzle to make this generic):

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

这篇关于NSUserDefaults:以可读格式存储仅具有属性列表兼容类型的结构/类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆