Отображение словаря в массив структур в Swift - PullRequest
0 голосов
/ 26 января 2019

Мое приложение должно представлять массив предустановок, где предустановка представлена ​​следующей структурой:

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

Используя NSUserDefaultsController, NSTableView и CocoaBindings, я смог создать окно настроек, где я могу добавлять и удалять пресеты, а также редактировать их. Они сохраняются в списке UserDefaults следующим образом:

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

Код в получателе должен выполнить преобразование из массива 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)?

Ответы [ 2 ]

0 голосов
/ 27 января 2019

Эта сущность позволила мне решить проблему с несколькими изменениями:

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

Теперь я могу реализовать вычисленные геттеры и сеттеры для всех моих свойств как таковые:

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

    set {
       setter(newValue, presetsKey)
    }
}

Это также работает для типов, не являющихся массивами.

0 голосов
/ 26 января 2019

Вы можете использовать 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)
...