从Swift 4中的结构获取所有关键路径

时间:2017-10-01 02:33:46

标签: swift swift4

我们说我有那个结构:

struct MyStruct {
    let x: Bool
    let y: Bool
}

在Swift 4中,我们现在可以使用myStruct[keyPath: \MyStruct.x]界面访问它的属性。

我需要的是一种访问其所有关键路径的方法,例如:

extension MyStruct {

    static func getAllKeyPaths() -> [WritableKeyPath<MyStruct, Bool>] {
        return [
            \MyStruct.x,
            \MyStruct.y
        ]
    }

}

但是,很明显,没有我必须手动声明数组中的每个属性。

我怎样才能做到这一点?

3 个答案:

答案 0 :(得分:3)

免责声明:

请注意,以下代码仅用于教育目的,并且不应在实际应用中使用,并且如果包含以下错误/怪异行为,则可能会包含很多错误: KeyPath是这样使用的。

答案:

我不知道您的问题今天是否仍然有用,但是挑战很有趣:)

实际上,可以使用镜像API。

KeyPath API当前不允许我们从字符串初始化新的KeyPath,但它确实支持字典“解析”。

这里的想法是建立一个使用镜像API描述struct的字典,然后遍历密钥以构建KeyPath数组。

Swift 4.2游乐场:

protocol KeyPathListable {
  // require empty init as the implementation use the mirroring API, which require
  // to be used on an instance. So we need to be able to create a new instance of the 
  // type.
  init()

  var _keyPathReadableFormat: [String: Any] { get }
  static var allKeyPaths: [KeyPath<Foo, Any?>] { get }
}

extension KeyPathListable {
  var _keyPathReadableFormat: [String: Any] {
    let mirror = Mirror(reflecting: self)
    var description: [String: Any] = [:]
    for case let (label?, value) in mirror.children {
      description[label] = value
    }
    return description
  }

  static var allKeyPaths: [KeyPath<Self, Any?>] {
    var keyPaths: [KeyPath<Self, Any?>] = []
    let instance = Self()
    for (key, _) in instance._keyPathReadableFormat {
      keyPaths.append(\Self._keyPathReadableFormat[key])
    }
    return keyPaths
  }
}

struct Foo: KeyPathListable {
  var x: Int
  var y: Int
}

extension Foo {
  // Custom init inside an extension to keep auto generated `init(x:, y:)`
  init() {
    x = 0
    y = 0
  }
}

let xKey = Foo.allKeyPaths[0]
let yKey = Foo.allKeyPaths[1]

var foo = Foo(x: 10, y: 20)
let x = foo[keyPath: xKey]!
let y = foo[keyPath: yKey]!

print(x)
print(y)

请注意,打印出的输出并非总是以相同的顺序出现(可能是由于镜像API所致,但不太确定)。

答案 1 :(得分:2)

修改rraphael的答案后,我在Swift论坛上对此进行了询问。

可能,在这里进行讨论:

Getting KeyPaths to members automatically using Mirror

此外,Swift for TensorFlow团队已将其内置到Swift for TensorFlow中,这可能会使其成为纯Swift:

Dynamic property iteration using key paths

答案 2 :(得分:0)

我提出解决方案。使用合并框架时,它具有可以正确处理@Published值的优点。

为清楚起见,它是我真正拥有的简化版本。在完整版中,我将一些选项传递给Mirror.allKeyPaths()函数以更改行为(例如,枚举子词典中的结构和/或类属性)。

  • 第一个Mirror扩展提出了一些简化属性枚举的功能。
  • 第二个扩展实现了keyPaths字典的创建,替换 @发布的属性,按正确的名称和值
  • 最后一部分是KeyPathIterable协议,该协议添加了枚举 关联对象的功能

swift

// MARK: - Convenience extensions

extension String {
    /// Returns string without first character
    var byRemovingFirstCharacter: String {
        guard count > 1 else { return "" }
        return String(suffix(count-1))
    }
}

// MARK: - Mirror convenience extension

extension Mirror {
    
    /// Iterates through all children
    static func forEachProperty(of object: Any, doClosure: (String, Any)->Void) {
        for (property, value) in Mirror(reflecting: object).children where property != nil {
            doClosure(property!, value)
        }
    }
    
    /// Executes closure if property named 'property' is found
    ///
    /// Returns true if property was found
    @discardableResult static func withProperty(_ property: String, of object: Any, doClosure: (String, Any)->Void) -> Bool {
        for (property, value) in Mirror(reflecting: object).children where property == property {
            doClosure(property!, value)
            return true
        }
        return false
    }
    
    /// Utility function to determine if a value is marked @Published
    static func isValuePublished(_ value: Any) -> Bool {
        let valueTypeAsString = String(describing: type(of: value))
        let prefix = valueTypeAsString.prefix { $0 != "<" }
        return prefix == "Published"
    }
}

// MARK: - Mirror extension to return any object properties as [Property, Value] dictionary

extension Mirror {
    
    /// Returns objects properties as a dictionary [property: value]
    static func allKeyPaths(for object: Any) -> [String: Any] {
        var out = [String: Any]()
        
        Mirror.forEachProperty(of: object) { property, value in
            // If value is of type Published<Some>, we transform to 'regular' property label and value
            if Self.isValuePublished(value) {
                Mirror.withProperty("value", of: value) { _, subValue in
                    out[property.byRemovingFirstCharacter] = subValue
                }
            } else {
                out[property] = value
            }
        }
        return out
    }
}

// MARK: - KeyPathIterable protocol

protocol KeyPathIterable {
    
}

extension KeyPathIterable {
    /// Returns all object properties
    var allKeyPaths: [String: Any] {
        return Mirror.allKeyPaths(for: self)
    }
}