将字典映射到 Swift 中的结构数组
Mapping a dictionary to an array of structs in Swift
我的应用需要表示一组预设,其中一个预设由以下结构表示:
struct Preset: Codable {
var name: String
var value: Int
}
使用 NSUserDefaultsController
、NSTableView
和 CocoaBindings,我能够创建首选项 window,我可以在其中添加和删除预设,以及编辑它们。它们被持久化到 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>
我希望使用自然表示法在我的代码中访问这些数据。基本上,我想要一个带有计算 属性 的设置单例,如下所示:
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
但我对这个解决方案并不满意。是否有更紧凑和通用的解决方案(适用于类似上下文中的任何结构,而无需硬编码结构 属性 名称,例如 name
和 value
)?
您可以使用 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)
这个 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: [=10=], 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: [=10=], format: .binary, options: 0) }
return try data.map { try PropertyListDecoder().decode(type, from: [=10=]) }
}
}
}
使用它,我可以定义以下通用函数:
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)
}
}
它也适用于非数组类型。
我的应用需要表示一组预设,其中一个预设由以下结构表示:
struct Preset: Codable {
var name: String
var value: Int
}
使用 NSUserDefaultsController
、NSTableView
和 CocoaBindings,我能够创建首选项 window,我可以在其中添加和删除预设,以及编辑它们。它们被持久化到 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>
我希望使用自然表示法在我的代码中访问这些数据。基本上,我想要一个带有计算 属性 的设置单例,如下所示:
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
但我对这个解决方案并不满意。是否有更紧凑和通用的解决方案(适用于类似上下文中的任何结构,而无需硬编码结构 属性 名称,例如 name
和 value
)?
您可以使用 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)
这个 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: [=10=], 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: [=10=], format: .binary, options: 0) }
return try data.map { try PropertyListDecoder().decode(type, from: [=10=]) }
}
}
}
使用它,我可以定义以下通用函数:
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)
}
}
它也适用于非数组类型。