55

So I am trying to get the Actual Variable Name as String in Swift, but have not found a way to do so... or maybe I am looking at this problem and solution in a bad angle.

So this is basically what I want to do:

var appId: String? = nil

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

Unfortunately I have not been able to find in apple docs anything that is close to this but this:

varobj.self or reflect(var).summary 

however, this gives information of what is inside the variable itself or the type of the variable in this case being String and I want the Actual name of the Variable.

jscs
  • 63,694
  • 13
  • 151
  • 195
S.H.
  • 2,833
  • 3
  • 26
  • 28
  • 2
    What are you actually trying to achieve from having the name of a variable? This isn't JavaScript or Python. Dynamic languages like those keep that sort of information around at runtime. In Swift, variable names are purely a convenience for the programmer (so as to give human names to memory addresses), and they don't exist in the program at runtime. – Alexander Jun 07 '17 at 16:51
  • 1
    @Alexander for NSPredicates it is useful :) – J. Doe Jan 18 '19 at 08:18
  • 2
    The answers seem to be to the question, "How can I get the property and method names from a Class, meta to to just knowing them." But, I think the original question was more like bob.dofunction(ted), how can dofunction find the strings "bob" and "ted". I am writing TUITest Code that self generates code to the output, it would be nice to have it know it's name. But, he marked it as the correct answer so he must have got it to work. Also, where is the return value for NSPredicate that answers the question? – Sojourner9 Jan 29 '19 at 23:04
  • Did you get this to work with the name of a method or function? I know how to get this to work with the name of a property or variable, but I'd like to get the name of a method or function. – daniel Jan 23 '22 at 05:49

7 Answers7

58

This is officially supported in Swift 3 using #keyPath()

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

Example usage would look like:

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

In Swift 4 we have something even better: \KeyPath notation

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")

The shorthand is a welcome addition, and being able to reference keypaths from a variable is extremely powerful

pkamb
  • 33,281
  • 23
  • 160
  • 191
Andrew
  • 1,279
  • 2
  • 13
  • 19
  • Thanks my hero! I would just complete saying the 2 methods `value(forKeyPath:)` and `setValue(_, forKeyPath)` enable to perform change through this #keyPath() use. IMO this should be the new accepted answer for Swift 3. – Tulleb Aug 01 '16 at 12:09
  • 4
    What is the variable name in this example, and where is it sent/given as a string? – Confused Nov 20 '16 at 23:15
  • The "variable" is not a variable at all. That is a class/struct defined somewhere else in the code. Something like `class Person { let firstName: String }`. If the example looks strange to you I suggest reading up on [NSPredicates](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html), they are very powerful when working with collections – Andrew Nov 25 '16 at 23:54
  • 10
    This seems to require that the type extend an NSObject? Is there something similar that would work on a "pure Swift" class? – BonanzaDriver Dec 09 '16 at 19:41
  • 3
    This also only works on actual class and struct properties. It will not work on local variables or local constants. – BTRUE Apr 03 '17 at 17:39
  • Do you know how to do this for NSObject's? – ChikabuZ Oct 27 '17 at 09:35
  • 12
    I am a little confused about the Swift 4 thing. The resulting `keyPath` is not a String directly, which the Objective-C runtime expects for the predicate's initializer. If `Person` is, for example, a Core Data generated subclass of NSManagedObject this doesn't work like that, as `keyPath` can't be converted to the "Objective-C-like" key, i.e. a String (or `NSString`). Am I missing something? – Gero Dec 19 '17 at 12:23
  • @Gero is right I think. From the Swift4 solution we get something like KeyPath. We don't get the String directly like in the Swift3 solution that depends on Objective-C. – Joel May 04 '23 at 09:46
29

As per the updated from this answer, it is supported in Swift 3 via #keyPath

NSPredicate(format: "%K == %@", #keyPath(Person.firstName), "Andrew")
Community
  • 1
  • 1
ColinE
  • 68,894
  • 15
  • 164
  • 232
  • i'd be dam... thanks for the heads up. I was afraid of this because of how new the language is. Hmm is there any way to be able to do this kind of mapping? – S.H. Sep 23 '14 at 22:29
  • If you need property or variable meta-data you are going to have to handle it yourself. – ColinE Sep 23 '14 at 22:30
  • 39
    Am I missing something, or does this not give the answer to the question, which I thought was 'How do you get the name of a variable? For instance, if I have a variable named `foo` (doesn't matter its type) I thought the question was 'how do you get the string "foo" to store it in another variable? As an example, here's how you would do it in C#: `string nameOfFoo = nameof(foo);` Now, the value stored in `nameOfFoo` is the string "foo" – Mark A. Donohoe Dec 19 '17 at 15:55
  • 1
    @IdoCohen's answer should be the accepted answer. He uses Mirror to access the name of the variable. – regina_fallangi Apr 29 '19 at 19:44
  • It does answer the question doesn't it? \#keyPath(Person.firstname) returns the string "firstName" which is "the Actual Variable Name as String in Swift" I put this into a playground and got the expected result. – Brett Jul 02 '19 at 05:23
23

This is my solution

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 ?? "")
}

print will be

name
lastName
Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145
Ido Cohen
  • 621
  • 5
  • 16
4

This works:

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)
}
cdeerinck
  • 688
  • 6
  • 17
2

Completing the accepted answer for extensions:

  1. The property needs to be @objc.
    var appId: String? {
        ....
    }
  1. You need to use #keyPath syntax, \ notation is not supported yet for extensions.
    #keyPath(YourClass.appId)
Calin Drule
  • 2,899
  • 1
  • 15
  • 12
1

I've come up with a swift solution, however unfortunately it doesn't work with Ints, Floats, and Doubles I believe.

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)

It compares memory addresses for an object's properties and sees if any match. The reason it doesn't work for Ints, Floats, and Doubles because they're not of type anyobject, although you can pass them as anyobject, when you do so they get converted to NSNumbers. therefore the memory address changes. they talk about it here.

For my app, it didn't hinder me at all because I only needed it for custom classes. So maybe someone will find this useful. If anyone can make this work with the other datatypes then that would be pretty cool.

Community
  • 1
  • 1
David Rees
  • 6,792
  • 3
  • 31
  • 39
  • I always get "" empty string, when i execute for my domain class variables... Can u help me to debug cos i don't have a deeper understanding over mirror and other stuffs? @david rees – vivin Jan 08 '16 at 21:49
  • When you say domain class variables what do you mean? I cant help you if you don't provide me with an example. – David Rees Jan 11 '16 at 23:27
  • Please find the ViewController file in the below project. https://github.com/vivinjeganathan/ErrorHandling/tree/Closures-Refactor – vivin Jan 13 '16 at 03:20
-1

The best solution is Here

From given link

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;
}

}

Dattatray Deokar
  • 1,923
  • 2
  • 21
  • 31