如何从 Swift 4 智能键路径语法(例如,\Foo.bar
)获取
字符串值?此时我对任何方式都很好奇,即使它很复杂也没关系。
我喜欢将类型信息与智能密钥路径相关联的想法。但并非所有 API 和第三方都已存在。
有一种通过
#keyPath()
进行编译时验证来获取属性名称字符串的旧方法。在 Swift 4 中使用 #keyPath()
,你必须将属性声明为 @objc
,这是我宁愿避免的事情。
聚会有点晚了,但我偶然发现了一种至少从
NSObject
子类获取关键路径字符串的方法:
NSExpression(forKeyPath: \UIView.bounds).keyPath
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 */
考虑到维护成本较高等,我不会真正推荐这种用于生产代码的解决方案。但既然你很好奇,这至少为你提供了一条前进的道路;)
对于 Objective-C 类上的 Objective-C 属性,您可以使用
_kvcKeyPathString
属性 来获取它。
但是,Swift 键路径可能没有 String 等效项。 Swift 键路径的既定目标是它们不需要将字段名称包含在可执行文件中。关键路径可能可以表示为要获取的字段的偏移序列,或者要调用对象的闭包。
当然,这与你自己避免声明属性的目标直接冲突
@objc
。我相信没有内置的设施可以做你想做的事。
扩展 @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"
我最近需要这样做,我想确保从编译器获得静态类型检查,而不需要对属性名称进行硬编码。
如果您的属性暴露给 Objective-C(即 @objc),您可以使用
#keyPath
字符串表达式。例如,您可以执行以下操作:
#keyPath(Foo.bar)
#keyPath(CALayer.postion)
参见文档
聚会已经很晚了,但这是我在 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
}
}
我创建了这个扩展来获取属性名称。也可以作为包的一部分使用。
extension KeyPath {
var propertyAsString: String {
"\(self)".components(separatedBy: ".").last ?? ""
}
}