NSUserDefaults: хранение структуры / класса только с типами, совместимыми со списком свойств, в читаемом формате - PullRequest
0 голосов
/ 27 января 2019

Предположим, что словарь хранится в UserDefaults в соответствии со следующим кодом:

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

Результат выполнения этой команды:

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

Я хотел бы вызвать следующий код и получить те же результаты, что и выше (данные, сохраненные в листе, используя точно такой же макет):

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, без жесткого кодирования свойств структуры, таких как name и value в данном случае)

Ответы [ 3 ]

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

Одним из решений было бы написать на Struct функцию, которая преобразует ее в словарь.

struct Preset {
     var name: String
     var value: Int

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

Затем, чтобы сохранить его в UserDefaults, вы можете просто сделать это:

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

Надеюсь, это поможет!

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

Вы можете создать протокол-способ, который решит вашу проблему.

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 {}
0 голосов
/ 27 января 2019

Следующее расширение 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)
    }
}
...