将字典映射到 Swift 中的结构数组

Posted

技术标签:

【中文标题】将字典映射到 Swift 中的结构数组【英文标题】:Mapping a dictionary to an array of structs in Swift 【发布时间】:2019-01-26 18:39:53 【问题描述】:

我的应用需要表示一组预设,其中预设由以下结构表示:

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

使用 NSUserDefaultsControllerNSTableView 和 CocoaBindings,我能够创建一个首选项窗口,我可以在其中添加和删除预设,以及编辑它们。它们被持久化到UserDefaults plist,如下所示:

<plist version="1.0">
<dict>
    <key>presets</key>
    <array>
        <dict>
            <key>name</key>
            <string>A preset</string>
            <key>value</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>name</key>
            <string>Another preset</string>
            <key>value</key>
            <integer>2</integer>
        </dict>
    </array>
</dict>
</plist>

我希望使用自然符号在我的代码中访问这些数据。基本上,我想要一个带有这样计算属性的 Settings 单例:

class Settings: NSObject 
    static let sharedInstance = Settings()
    private let presetsKey = "presets"

    override private init() 
        UserDefaults.standard.register(defaults: [
            presetsKey: [ ["name": "Default preset", "value": 0] ],
        ])

        super.init()
    

    var presets: [Preset] 
        get 
            // Missing code goes here
        
    

getter 中的代码应该执行从 preset UserDefaults 数组到 [Preset] 的映射,而不是 // Missing code goes here 注释。这是我想使用的符号示例:

let firstPresetName = Settings.sharedValue.preset[0].name
let firstPresetValue = Settings.sharedValue.preset[0].value
print("The first preset's name is \(firstPresetName\) and its value is \(firstPresetValue\)")

我写了这个,它有效:

let presets = UserDefaults.standard.array(forKey: presetsKey) as! [[String: Any]]
var result = [Preset]()
for preset in presets 
    result.append(ControlParameters(name:preset["name"] as! String, value:preset["value"] as! Int))

return result

但我对这个解决方案并不满意。是否有更紧凑和通用的解决方案(适用于类似上下文中的任何结构,而无需硬编码结构属性名称,例如 namevalue)?

【问题讨论】:

【参考方案1】:

您可以使用PropertyListDecoder 直接解码您的属性列表文件:

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


struct Presets : Codable 
    let presets: [Preset]


let decoder = PropertyListDecoder()
let presets = try? decoder.decode(Presets.self, from: yourPropertyListData)

【讨论】:

【参考方案2】:

这个gist 让我解决了这个问题,但做了一些修改:

extension UserDefaults 
    func encode<T: Encodable>(_ value: T?, forKey key: String) throws 
        switch T.self 
        case is Float.Type, is Double.Type, is String.Type, is Int.Type, is Bool.Type:
            set(value!, forKey: key)
        default:
            let data = try value.map(PropertyListEncoder().encode)
            let any = data.map  try! PropertyListSerialization.propertyList(from: $0, options: [], format: nil) 

            set(any, forKey: key)
        
    

    func decode<T: Decodable>(_ type: T.Type, forKey key: String) throws -> T? 
        switch T.self 
        case is Float.Type, is Double.Type, is String.Type, is Int.Type, is Bool.Type:
            return (value(forKey: key) as! T)
        default:
            let any = object(forKey: key)
            let data = any.map  try! PropertyListSerialization.data(fromPropertyList: $0, format: .binary, options: 0) 

            return try data.map  try PropertyListDecoder().decode(type, from: $0) 
        
    

使用它,我可以定义以下通用函数:

private func getter<T: Codable>(key: String) -> T 
    let result = try! UserDefaults.standard.decode(T.self, forKey: key)
    return result!


private func setter<T: Codable>(_ newValue: T, forKey key: String) 
    try! UserDefaults.standard.encode(newValue, forKey: key)

现在我可以为我的所有属性实现计算的 getter 和 setter:

var presets: [Preset] 
    get 
       return getter(presetsKey)
    

    set 
       setter(newValue, presetsKey)
    

它也适用于非数组类型。

【讨论】:

为什么投反对票?如果这是一个糟糕的解决方案,我很想知道具体细节,因为我还是 Swift 的初学者,还有很多东西要学。

以上是关于将字典映射到 Swift 中的结构数组的主要内容,如果未能解决你的问题,请参考以下文章

从字典数组映射到 Swift 中的数字数组

Swift 4 通过外键将数组映射到字典

将嵌套字典写入 plist (Swift) 时的类型问题

哈希表——swift字典的实现原理

Swift映射嵌套字典以交换外部和内部键

swift:修改字典中的数组