获取一个Swift变量的实际名称作为字符串

56

我想在Swift中以字符串形式获取实际变量名,但是还没有找到方法......或者可能是我从错误的角度来看待这个问题和解决方案。

所以这基本上是我想做的事情:

var appId: String? = nil

//This is true, since appId is actually the name of the var appId
if( appId.getVarName = "appId"){
    appId = "CommandoFurball"
}

很遗憾,我在苹果文档中没有找到接近此内容的东西,只有这个:

varobj.self or reflect(var).summary 

然而,这提供了变量本身或变量类型(在这种情况下为String)的信息,我想要的是变量的实际名称。


2
你实际上想从变量名中获得什么?这不是JavaScript或Python。像这样的动态语言在运行时保留了那种信息。在Swift中,变量名纯粹是为了方便程序员(以便将人类名称赋予内存地址),并且它们在运行时不存在于程序中。 - Alexander
1
@Alexander 对于 NSPredicates 很有用 :) - J. Doe
2
答案似乎是回答“如何从一个类中获取属性和方法名称,而不仅仅是知道它们的元数据。”但我认为原始问题更像是bob.dofunction(ted),dofunction如何找到字符串“bob”和“ted”。我正在编写TUITest代码,自动生成代码输出,它知道它的名称会很好。但他将其标记为正确答案,所以他一定让它工作了。此外,NSPredicate的返回值在哪里回答了这个问题? - Sojourner9
你能用方法或函数的名称让它工作吗?我知道如何使用属性或变量的名称让它工作,但我想获取方法或函数的名称。 - daniel
7个回答

58

使用#keyPath()可以在官方支持的 Swift 3 中实现此功能。

https://github.com/apple/swift-evolution/blob/master/proposals/0062-objc-keypaths.md

例如,使用方法如下:

NSPredicate(format: "%K == %@", #keyPath(Person.firstName), "Wendy")

Swift 4 中,我们拥有了更好的东西:\KeyPath 符号。

https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md

NSPredicate(format: "%K == %@", \Person.mother.firstName, "Wendy")

// or

let keyPath = \Person.mother.firstName
NSPredicate(format: "%K == %@", keyPath, "Andrew")

使用速记方式是一个值得欢迎的补充,能够从变量中引用关键路径非常强大。


谢谢我的英雄!我只是想完成一下说,2种方法value(forKeyPath:)setValue(_, forKeyPath)可以通过使用#keyPath()来进行更改。在我看来,这应该是Swift 3的新接受答案。 - Tulleb
4
这个例子中的变量名是什么,并且它在哪里被作为字符串发送/给出? - Confused
10
需要类型扩展 NSObject 才能实现这个吗?有没有类似的方法适用于“纯 Swift”类? - BonanzaDriver
3
这仅适用于实际的类和结构属性,无法在局部变量或常量上使用。 - BTRUE
12
我对 Swift 4 的一些事情感到有些困惑。生成的 keyPath 不是 Objective-C 运行时所期望的谓词初始化器的字符串类型。如果例如 Person 是一个 NSManagedObject 的 Core Data 生成子类,那么它就不会按照这种方式工作,因为 keyPath 无法转换成 "Objective-C-like" 形式的键,即字符串(或 NSString)。我是否漏掉了什么? - Gero
显示剩余3条评论

29

根据这个答案的更新,Swift 3中支持使用#keyPath

NSPredicate(format: "%K == %@", #keyPath(Person.firstName), "Andrew")

我会尽力的...谢谢你提醒我。因为这门语言很新,所以我一直担心这个问题。嗯,有没有什么方法可以做到这种映射呢? - S.H.
如果您需要属性或变量元数据,您将不得不自己处理它。 - ColinE
39
我有所遗漏,还是这段文字没有回答问题?我原以为问题是“如何获得变量名称?”例如,如果我有一个名为foo的变量(类型无关),我认为问题是“如何获取字符串“foo”并将其存储到另一个变量中?”以下是在C#中执行此操作的示例: string nameOfFoo = nameof(foo); 现在,nameOfFoo中存储的值是字符串“foo”。 - Mark A. Donohoe
1
@IdoCohen的回答应该被接受。他使用Mirror来访问变量的名称。 - regina_fallangi
它回答了问题,不是吗?#keyPath(Person.firstname)返回字符串“firstName”,这是“Swift中实际变量名称作为字符串”。我将其放入游乐场并获得了预期结果。 - Brett

25

这是我的解决方案

class Test { 
    var name: String = "Ido"
    var lastName: String = "Cohen"
}

let t = Test()
let mirror = Mirror(reflecting: t)

for child in mirror.children {
    print(child.label ?? "")
}

将会是打印

name
lastName

这应该是被接受的答案。我刚在Swift 5.5中测试过它,它可以工作。 - Arturo

4

这个是可行的:

struct s {
    var x:Int = 1
    var y:Int = 2
    var z:Int = 3
}

var xyz = s()

let m = Mirror(reflecting: xyz)

print(m.description)
print(m.children.count)
for p in m.children {
    print(p.label as Any)
}

2

针对扩展的已接受答案,需要完成以下步骤:

  1. 该属性需要添加@objc标记。
    @objc var appId: String? {
        ....
    }
  1. 你需要使用#keyPath语法,目前不支持使用反斜杠符号进行扩展。
    #keyPath(YourClass.appId)

1
我想出了一个快速解决方案,但是很不幸,它不能处理Ints、Floats和Doubles。
func propertyNameFor(inout item : AnyObject) -> String{
    let listMemAdd = unsafeAddressOf(item)
    let propertyName =  Mirror(reflecting: self).children.filter { (child: (label: String?, value: Any)) -> Bool in
        if let value = child.value as? AnyObject {
            return listMemAdd == unsafeAddressOf(value)
        }
        return false
        }.flatMap {
            return $0.label!
    }.first ?? ""
    
    return propertyName
}

var mutableObject : AnyObject = object
let propertyName = MyClass().propertyNameFor(&mutableObject)

它比较对象属性的内存地址,看是否有匹配项。它不能用于Ints、Floats和Doubles,因为它们不是任何对象类型,虽然您可以将它们作为任何对象传递,但这样做会将它们转换为NSNumbers,因此内存地址会改变。他们在这里讨论了这个问题。 对于我的应用程序,它并没有妨碍我,因为我只需要用它来处理自定义类。所以也许有人会觉得这很有用。如果有人能让它适用于其他数据类型,那就太棒了。

当我执行我的域类变量时,我总是得到空字符串""... 你能帮我调试一下吗?因为我对镜像和其他东西没有深入的了解。@david rees - vivin
当你说领域类变量时,你指的是什么?如果你不给我一个例子,我就无法帮助你。 - David Rees
请在以下项目中找到ViewController文件。https://github.com/vivinjeganathan/ErrorHandling/tree/Closures-Refactor - vivin

-1

最佳解决方案是这里

从给定的链接

import Foundation

extension NSObject {
//
// Retrieves an array of property names found on the current object
// using Objective-C runtime functions for introspection:
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
//
func propertyNames() -> Array<String> {
    var results: Array<String> = [];

    // retrieve the properties via the class_copyPropertyList function
    var count: UInt32 = 0;
    var myClass: AnyClass = self.classForCoder;
    var properties = class_copyPropertyList(myClass, &count);

    // iterate each objc_property_t struct
    for var i: UInt32 = 0; i < count; i++ {
        var property = properties[Int(i)];

        // retrieve the property name by calling property_getName function
        var cname = property_getName(property);

        // covert the c string into a Swift string
        var name = String.fromCString(cname);
        results.append(name!);
    }

    // release objc_property_t structs
    free(properties);

    return results;
}

}


1
这个解决方案没有返回父类中的属性名称,对吗? - vivin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接