我们说我有那个结构:
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
]
}
}
但是,很明显,没有我必须手动声明数组中的每个属性。
我怎样才能做到这一点?
答案 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:
答案 2 :(得分:0)
我提出解决方案。使用合并框架时,它具有可以正确处理@Published
值的优点。
为清楚起见,它是我真正拥有的简化版本。在完整版中,我将一些选项传递给Mirror.allKeyPaths()
函数以更改行为(例如,枚举子词典中的结构和/或类属性)。
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)
}
}