0

How do we print variable name in swift? It's similar to what this question discusses Get a Swift Variable's Actual Name as String I can't figure out from above QA how to print it in swift for my need as described below

struct API {
    static let apiEndPoint = "example.com/profile?"
    static let apiEndPoint1 = "example.com/user?"
 }

doSomething(endPoint: API.apiEndPoint)
doSomething(endPoint: API.apiEndPoint1)

func doSomething(endPoint: String) {
    // print the variable name which should be apiEndPoint or endPoint1
} 
Raymond
  • 1,108
  • 13
  • 32
  • 1
    `print("apiEndPoint")`? – Quinn Jan 30 '20 at 19:16
  • 1
    You should use a dictionary instead of a variable, if you don't want to hardcode variable names in print calls. – Eli Korvigo Jan 30 '20 at 19:17
  • 6
    There's no way to do what you've literally asked for, but there are several approaches to doing similar things. What are you trying to acheve? – Rob Napier Jan 30 '20 at 19:17
  • I've updated my question. I said I just want to print the variable name. – Raymond Jan 30 '20 at 19:21
  • I'm decoding JSON via a common function and I want to print the endPoint variable name itself so that I can easily search for it in debug window in xCode – Raymond Jan 30 '20 at 19:25
  • As @RobNapier said, this cannot be done. What you _can_ do however is to create a `String` backed enum for your endpoints and use that instead. – Alladinian Jan 30 '20 at 19:27
  • I've updated question which uses Struct. Can we print the variable name now? – Raymond Jan 30 '20 at 19:29
  • 1
    No. From the point of view of your `doSomething` method, all it sees is the **value** of its `endPoint` parameter. Where that came from, a variable, a constant, a function call, whatever, is not visible to it. – Gereon Jan 30 '20 at 19:36

3 Answers3

5

You could change your struct to an enum

enum API: String {
    case apiEndPoint = "example.com/profile?"
    case apiEndPoint1 = "example.com/user?"
 }


func doSomething(endPoint: API) {
    print("\(endPoint): \(endPoint.rawValue)")
}

Example

doSomething(endPoint: .apiEndPoint)

apiEndPoint: example.com/profile?

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • You are spot on but for me changing struct to enum will require a lot of code changes. – Raymond Jan 30 '20 at 20:00
  • @Raymond Maybe so but an enum could give you code that is easier to read and more type safe but of course I have no idea how much work it would be – Joakim Danielson Jan 30 '20 at 20:07
  • @Raymond More code changes than any other method? This is exactly the sort of thing enums are meant for, anything else is just making it needlessly complicated. – John Montgomery Jan 30 '20 at 20:17
2

You could use Mirror for reflection and do something silly like this:

struct API {
    let apiEndPoint = "example.com/profile?"
    let apiEndPoint1 = "example.com/user?"
}

func doSomething(api: API, endPoint: String) {
    let mirror = Mirror(reflecting: api)

    for child in mirror.children {
        if (child.value as? String) == endPoint {
            print(child.label!) // will print apiEndPoint
        }
    }
}

let api = API()

doSomething(api: api, endPoint: api.apiEndPoint)
doSomething(api: api, endPoint: api.apiEndPoint1)

But I would never recommend doing something like this, and using an enum like the other answer suggested is probably the way to go.

Quinn
  • 7,072
  • 7
  • 39
  • 61
  • Yes I've seen this solution but it's not practical as I've hundreds of endpoints and I'll end up writing hundreds of if else conditions. – Raymond Jan 30 '20 at 19:51
  • 1
    @Raymond don't know why this would make you need 100s of if/else's - it will work for any number of endpoints with no need for extra ifs – Quinn Jan 30 '20 at 19:55
  • Sorry I missed it. Yes you are right. However it's not working for me. Please note that I'm not passing api to function, I'm only passing endPoint. I tired by doing let mirror = Mirror(reflecting: API()) but it didn't work, control doesn't go inside the if condition. – Raymond Jan 30 '20 at 20:06
  • 1
    @Raymond yeah, I had to add api as a parameter if you wanted it to work right, which is another reason I would suggest an enum instead since it is much cleaner. That said if the endpoints are constant let values that never change, I don't see why `let mirror = Mirror(reflecting: API())` would not work, it works for me in my playground. – Quinn Jan 30 '20 at 20:10
  • Control doesn't go inside "for child in mirror.children {" – Raymond Jan 30 '20 at 20:13
  • 1
    @Raymond Is `mirror.children` empty? Any chance your API let's are static? – Quinn Jan 30 '20 at 20:17
  • Sorry yes, they are static. I've updated my question. – Raymond Jan 30 '20 at 20:22
1

I like Quinn's approach, but I believe it can be done more simply:

struct API {
    let apiEndPoint = "example.com/profile?"
    let apiEndPoint1 = "example.com/user?"

    func name(for string: String) -> String? {
        Mirror(reflecting: self).children
            .first { $0.value as? String == string }?.label
    }
}

func doSomething(endPoint: String) {
    print(API().name(for: endPoint)!)
}

let api = API()

doSomething(endPoint: api.apiEndPoint) // apiEndPoint
doSomething(endPoint: api.apiEndPoint1) // apiEndPoint1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • It crashes on print(API().name(for: endPoint)!). Thread 12: Fatal error: Unexpectedly found nil while unwrapping an Optional value. Please note that I've static variables in structure. Also I don't use api = API() to pass api value in the function. I directly pass API.endPoint as I've written in question. – Raymond Jan 30 '20 at 20:27
  • 1
    @Raymond it is because your variables are static, static variables don't appear in reflection so it is finding nothing and returning null – Quinn Jan 30 '20 at 20:28
  • Agreed; this is likely not solvable for static properties or globals without adding some piece of metadata somewhere (creating a lookup Dictionary or creating a struct with the name and the path, for example). – Rob Napier Jan 30 '20 at 20:32