Mark A. Donohoe
Mark A. Donohoe

Reputation: 30368

Is there a Swift equivalent of C#'s 'nameof()' function to get a variable or member's name at compile time?

Ok, there's an existing question here on S/O with the following title:

Swift: Get Variable Actual Name as String

By it's name, it seems that's exactly what I want. However, looking at the accepted answer (and the other non-accepted ones), they are referring to key path manipulation, which isn't what I'm after. (i.e. This is not a duplicate!)

In my case, I want the name of one variable to be stored in a second variable of type string.

In C#, this is trivial using nameof, like so...

int someVar = 3

string varName = nameof(someVar)
// 'varName' now holds the string value "someVar"

Note: nameof() executes at compile-time, not run-time so no reflection or anything else is needed. The compiler simply subs in the name of the variable as if someone manually typed its name as a string constant.

It's pretty handy when you, for instance, want to define a query object where your member names match the query parameters passed in a URL.

Here's a pseudo-code example (i.e. this clearly won't compile, but shows what I'm after.)

Also, please don't focus on the URL aspects of this. This is definitely *bad code*. In real code I'd use URLComponents, not string-append a URL like I'm doing here. Again, this is *only* illustrating my question, not building URLs.

struct queryObject{

    let userName  : String
    let highScore : Int

    var getUrl:String{
        return "www.ScoreTracker.com/postScore?\(nameof(userName))=\(userName)&\(nameof(highScore))=\(highScore)"
    }
}

Here's how you'd use it and what it would return:

let queryObject = QueryObject(userName:"Maverick", highScore:123456)

let urlString = queryObject.getUrl

The return value would be:

www.ScoreTracker.com/postScore?userName=Maverick&highScore=123456

The advantages of having access to a string representation of a variable, instead of using string constants are many:

  1. No hard-coded strings.
  2. You can use refactoring/renaming of symbols as usual and the generated strings track
  3. Allows you to keep your API and implementation in sync which aids in readability.

For instance, say the API changed the query param from userName to userId, all one would have to do is refactor the variable name and the output would update accordingly without having to manually touch any strings.

So, can this be done in Swift? Can you get the actual variable name and store it in a second variable?

Upvotes: 13

Views: 1919

Answers (2)

Alexander
Alexander

Reputation: 101

If you are only dealing with URLs, it is recommended to use URLComponents, which is better.

model:

struct QueryObject{

    var params = [String: String]()

    var getUrl: String{
        let result = "www.ScoreTracker.com/postScore"
        guard var url = URL(string: result) else { return result}
        url.appendQueryParameters(params)
        return url.absoluteString
    }

    init(params: [String: String]) {
        self.params = params
    }

}

then:

    let params = ["userName": "Maverick", "highScore": "123456"]
    let queryObject = QueryObject(params: params)
    let urlString = queryObject.getUrl
    DDLog(urlString) //www.ScoreTracker.com/postScore?userName=Maverick&highScore=123456

Github link

Upvotes: 1

C-Viorel
C-Viorel

Reputation: 1866

Yes you can. I've used this as a solution for the next scenario: When I print an object/struct in the console, I like to see the vars names along with associated values:

extension CustomStringConvertible {
    var description: String {
        var description = "\n          *****  \(type(of: self)) *****\n"
        let mirrorS = Mirror(reflecting: self)
        for child in mirrorS.children {
            if let propertyName = child.label {
                description += "   \(getSpaces(forWord: propertyName))\(propertyName):  \(child.value)\n"
            }
        }

        return description
    }
}

And to use this, let's have as an example the following struct:

public struct FavoriteApp: Codable, CustomStringConvertible, Hashable {
    let appId: String
    let appName: String
    let bundleId: String
    let image: NSImage
    let isMacApp: Bool
    let rating: String
    let screenshotUrls: [String]
    let sellerName: String
    let sellerUrl: String
    let releaseDate: String
    let genres: String
    let latestVersion: String

}

let anApp:FavoriteApp = getTheAppInfoFromServer()
print(anApp) 

// This will print into console: 
    //        *****  FavoriteApp *****
    //        appId:          1438547269
    //        appName:        Dynamic wallpaper
    //        bundleId:        com.minglebit.DynamicWallpaper
    //        image:           -not visible- (but is there, trust me :))
    //        isMacApp         true
    //        rating:          0
    //
    //        screenshotUrls:  https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/5c/08/b8/5c08b824-12eb-e0b6-22fb-0e234f396b9e/pr_source.jpg/800x500bb.jpg
    //                        https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/f3/34/0e/f3340e21-714c-0a17-4437-73ba1828cbba/pr_source.jpg/800x500bb.jpg
    //        sellerName:      MingleBit SRL
    //        sellerUrl:       https://minglebit.com/products/dynamicwallpaper.php
    //        releaseDate:     2018-10-18T14:59:47Z
    //        genres:          Entertainment, Lifestyle
    //        latestVersion:   2.3

So, to concentrate the answer, use Mirror(reflecting: object) to get what you want.

Upvotes: 1

Related Questions