将类型为 Any 的 Swift Encodable class 转换为字典

Convert Swift Encodable class typed as Any to dictionary

关于 ,我决定子 class 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 class 的东西,而不仅仅是具有两个属性 namevalue 的东西怎么办?

基本上,问题是从 class 创建字典,其中键是 属性 名称,值是这些属性的值。

我试过这样写:

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

我该如何解决这个问题才能编译?

您传递给 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)
}

不用说,这太恶心了。它是不灵活的、重复的样板文件。我不确定我是否真的可以推荐 使用它。这是对字面问题的回答,不一定是问题的解决方案。

问题是 . PropertyListEncoder's encode(_:) 方法需要一个 Value : Encodable 参数:

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

但是 Encodable 类型本身目前无法满足此约束(但在该语言的未来版本中可能会很好)。

如链接的问答(和 also here)中所述,解决此限制的一种方法是 打开 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.
    }
  }
}