Преобразовать Swift Encodable класс, типизированный как Any в словарь - PullRequest
0 голосов
/ 27 января 2019

В связи с my предыдущими вопросами я решил создать подкласс NSArrayController для достижения желаемого поведения.

class NSPresetArrayController: NSArrayController {
    override func addObject(_ object: Any) {
        if let preset = object as? Preset {
            super.addObject(["name": preset.name, "value": preset.value])
        } else {
            super.addObject(object)
        }
    }
}

Это работает, но что, если мне нужно что-то, что работает для любого класса Encodable, а не только одного с двумя свойствами, называемыми name и value?

В принципе, проблема заключается в создании словаряиз класса, где ключи - это имена свойств, а значения - значения этих свойств.

Я попытался написать что-то вроде этого:

class NSPresetArrayController: NSArrayController {
    override func addObject(_ object: Any) {
        if let encodableObject = object as? Encodable {
            let data = try! PropertyListEncoder().encode(encodableObject)
            let any = try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)

            super.addObject(any)
        }
    }
}

Однако я получаю компиляциюошибка:

Cannot invoke 'encode' with an argument list of type '(Encodable)'
1. Expected an argument list of type '(Value)'

Как это исправить, чтобы он компилировался?

Ответы [ 2 ]

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

Проблема в том, что протоколы не всегда соответствуют себе .PropertyListEncoder s encode(_:) метод ожидает аргумент Value : Encodable:

func encode<Value : Encodable>(_ value: Value) throws -> Data

Однако сам тип Encodable в настоящее время не может удовлетворить это ограничение (но вполне можетв следующей версии языка).

Как описано в связанных вопросах и ответах (и также здесь ), один из способов обойти это ограничение - open значение Encodable, чтобы выкопать конкретный конкретный тип, который мы можем заменить на Value.Мы можем сделать это с расширением протокола и использовать тип оболочки для его инкапсуляции:

extension Encodable {
  fileprivate func openedEncode(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.openedEncode(to: &container)
  }
}

Применительно к вашему примеру:

class NSPresetArrayController : NSArrayController {
  override func addObject(_ object: Any) {
    guard let object = object as? Encodable else { 
      // Not encodable, maybe do some error handling.
      return 
    }
    do {
      let encoded = try PropertyListEncoder().encode(AnyEncodable(object))
      let cocoaPropertyList = try PropertyListSerialization.propertyList(from: encoded, format: nil)
      super.addObject(cocoaPropertyList)
    } catch {
      // Couldn't encode. Do some error handling.
    }
  }
}
0 голосов
/ 27 января 2019

Тип значения, которое вы передаете encode(_:), должен быть конкретным типом, который реализует Encodable.Это означает, что вам нужно восстановить реальный тип объекта из Any, который у вас есть.Для того, чтобы привести, у вас должен быть статически определенный тип, к которому вы применяете.Другими словами, вы не можете сказать object as! type(of: object);вы должны сказать object as? MyClass (или в общем контексте вы можете сказать object as? T).

Поэтому я считаю, что единственный способ обойти это - статически перечислить типы, с которыми вы работаете,вот так:

import Foundation

struct S : Encodable {
    let i: Int
}

struct T : Encodable {
    let f: Float
}

struct U : Encodable {
    let b: Bool
}

func plistObject(from encodable: Any) -> Any? {
    let encoded: Data?
    switch encodable {
        case let s as S:
            encoded = try? PropertyListEncoder().encode(s)
        case let t as T:
            encoded = try? PropertyListEncoder().encode(t)
        case let u as U:
            encoded = try? PropertyListEncoder().encode(u)
        default:
            encoded = nil
    }

    guard let data = encoded else { return nil }

    return try? PropertyListSerialization.propertyList(from: data,
                                                       options: [],
                                                       format: nil)
}

Излишне говорить, что это довольно грубо.Это негибкий, повторяющийся шаблон.Я не уверен, что могу на самом деле рекомендовать его использование.Это ответ на буквальный вопрос, не обязательно решение проблемы.

...