I want to print some property names of a protocol in a type safe way. One way I have found is using a struct conforming to the protocol and then using key paths. However, is there also another way where I can directly use the property and print it?
protocol A {
var myName: Int { get }
}
print(....) // Result -> "myName"
I don’t know of a built-in way, but it doesn’t sound hard to write a macro equivalent to C# nameof. Unfortunately, that would mean setting up a macro library.
Indeed! In fact, it's the example given by the Swift Package Manager's macro template. They call it #stringify, and it could be used like:
protocol A {
var myName: Int { get }
}
extension A {
func demo() {
let _, propertyName = #stringify(myName)
print(propertyName)
}
}
It needs to be called within the scope of the A protocol though, since the contents inside the #stringify(...) needs to be valid Swift code. So:
myName or self.myName inside those parens works,
#stringify(A.myName) doesn't work, since there's no static property with that name on A
#stringify(myName) outside that scope doesn't work, since that would be referring to a global variable myName, and not the protocol's instanced property myName.
Inefficient, hacky and subject to break, but this works (for now)
protocol A {
var myName: Int { get }
}
func nameof<Receiver, _PropertyType>(_ keypath: KeyPath<Receiver, _PropertyType>) -> String {
let typeName = String(describing: Receiver.self)
print(typeName)
let kpDesc = keypath.debugDescription
let expectedPrefix = #"\\#(typeName)."#
guard kpDesc.hasPrefix(expectedPrefix) else {
// FIXME: The formatting of KeyPath.debugDescription changed... Handle this error, somehow.
return "Welp, expected \(kpDesc) to start with \(expectedPrefix), but it didn't."
}
return String(kpDesc.dropFirst(expectedPrefix.count))
}
let propertyName = nameof(\A.myName)
print(propertyName)
protocol MyProtocol {
var name: String { get }
var age: Int { get }
}
struct Person: MyProtocol {
var name: String
var age: Int
var address: String
}
func getProtocolPropertyNames<T: MyProtocol>(_ object: T) -> [String] {
let mirror = Mirror(reflecting: object)
var propertyNames: [String] = []
for child in mirror.children {
if let propertyName = child.label {
propertyNames.append(propertyName)
}
}
return propertyNames
}
let person = Person(name: "Alice", age: 30, address: "Wonderland")
let properties = getProtocolPropertyNames(person)
print(properties) // ["name", "age", "address"]