从 Swift 4 新的键路径语法获取字符串?

问题描述 投票:0回答:7

如何从 Swift 4 智能键路径语法(例如,\Foo.bar)获取

字符串值
?此时我对任何方式都很好奇,即使它很复杂也没关系。

我喜欢将类型信息与智能密钥路径相关联的想法。但并非所有 API 和第三方都已存在。

有一种通过

#keyPath()
进行编译时验证来获取属性名称字符串的旧方法。在 Swift 4 中使用
#keyPath()
,你必须将属性声明为
@objc
,这是我宁愿避免的事情。

ios swift reflection swift4 key-value-coding
7个回答
52
投票

聚会有点晚了,但我偶然发现了一种至少从

NSObject
子类获取关键路径字符串的方法:

NSExpression(forKeyPath: \UIView.bounds).keyPath

36
投票

简短的回答:你不能。

KeyPath
抽象旨在封装来自给定根类型的潜在嵌套属性 key path。因此,导出单个
String
值在一般情况下可能没有意义。

例如,假设导出的字符串是否应该被解释为 root 类型 的属性或其嵌套类型之一的成员?至少需要导出一个字符串数组来解决这种情况......

每种类型的解决方法。话虽如此,鉴于

KeyPath
符合
Equatable
协议,您可以自己提供自定义的 per type 解决方案。例如:

struct Auth {
    var email: String
    var password: String
}
struct User {
    var name: String
    var auth: Auth
}

为基于User的关键路径提供

扩展

extension PartialKeyPath where Root == User {
    var stringValue: String {
        switch self {
        case \User.name: return "name"
        case \User.auth: return "auth"
        case \User.auth.email: return "auth.email"
        case \User.auth.password: return "auth.password"
        default: fatalError("Unexpected key path")
    }
}

用途:

let name:  KeyPath<User, String> = \User.name
let email: KeyPath<User, String> = \User.auth.email
print(name.stringValue)  /* name */
print(email.stringValue) /* auth.email */

考虑到维护成本较高等,我不会真正推荐这种用于生产代码的解决方案。但既然你很好奇,这至少为你提供了一条前进的道路;)


26
投票

对于 Objective-C 类上的 Objective-C 属性,您可以使用

_kvcKeyPathString
属性 来获取它。

但是,Swift 键路径可能没有 String 等效项。 Swift 键路径的既定目标是它们不需要将字段名称包含在可执行文件中。关键路径可能可以表示为要获取的字段的偏移序列,或者要调用对象的闭包。

当然,这与你自己避免声明属性的目标直接冲突

@objc
。我相信没有内置的设施可以做你想做的事。


23
投票

扩展 @Andy Heard 的答案,我们可以扩展

KeyPath
以获得计算属性,如下所示:

extension KeyPath where Root: NSObject {
    var stringValue: String {
        NSExpression(forKeyPath: self).keyPath
    }
}

// Usage
let stringValue = (\Foo.bar).stringValue
print(stringValue) // prints "bar"


3
投票

我最近需要这样做,我想确保从编译器获得静态类型检查,而不需要对属性名称进行硬编码。

如果您的属性暴露给 Objective-C(即 @objc),您可以使用

#keyPath
字符串表达式。例如,您可以执行以下操作:

#keyPath(Foo.bar)
#keyPath(CALayer.postion)

参见文档


3
投票

聚会已经很晚了,但这是我在 Swift 中的解决方案:

extension PartialKeyPath {
    var keyPath: String {
        let me = String(describing: self)
        let rootName =  String(describing: Root.self)
        let removingRootName = me.components(separatedBy: rootName)
        var keyPathValue = removingRootName.last ?? ""
        if keyPathValue.first == "." { keyPathValue.removeFirst() }
        return keyPathValue
    }
}

或者,由于我相信

String(describing: KeyPath)
始终将字符串构造为
"\RootName.keyPathName"
,因此您可以使用 dropFirst 代替:

extension PartialKeyPath {
    var keyPath: String {
        let me = String(describing: self)
        let dropLeading =  "\\" + String(describing: Root.self) + "."
        let keyPath = "\(me.dropFirst(dropLeading.count))"
        return keyPath
    }
}

0
投票

我创建了这个扩展来获取属性名称。也可以作为包的一部分使用。

extension KeyPath {
    var propertyAsString: String {
        "\(self)".components(separatedBy: ".").last ?? ""
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.