3

Given a constructor such as:

required init (pTableName : String, pRecordKey : String, pStartAtRecord : Int){
    parameters.append(ChildElement(identifier: "pTableName", value: pTableName))
    parameters.append(ChildElement(identifier: "pRecordKey", value: pRecordKey))
    parameters.append(ChildElement(identifier: "pStartAtRecord", value: pStartAtRecord))
}

I want to extract the names of all argument variables such as pTableName, pRecordKey and so forth. Why exactly? First of all, I might have more then 30 different classes having different constructor arguments and passing those into the parameters array. Secondly, all of the constructors have a different signature, but mainly they do the same thing - create ChildElements and append them to the list of parameters - which afterwards constitutes a SOAP request for the web service.

By having the ability of getting the variable names (e.g. pTableName should produce "pTableName" : String), I would like to accomplish the following:

required init (pTableName : String, pRecordKey : String, pStartAtRecord : Int){
    appendToParameters([pTableName, pRecordKey, pStartAtRecord])
} 

which would then for each element of the array produce:

parameters.append(ChildElement(identifier: Reflection.getName(var), value : var))

While the structure of the ChildElement class is equal to:

class ChildElement : Element {

var attributes : [String : String] = [:]
var identifier : String
var value : AnyObject

var toString : String {
    return "<\(identifier) \(self.concatinateAttributesAsString())>\(value)</\(identifier)>"
}

required init (identifier : String, value : AnyObject) {
    self.identifier = identifier
    self.value = value
}

However, in Swift there is no possibility of getting the variable name using reflection and macros such as the one used in Objective-C:

#define variableName(var) [NSString stringWithFormat:@"%s",#var]

If I try to use the macro via an Objective C method, and then applied in Swift:

-(NSString *)getVarName:(id)var {
    return variableName(var)
}

then of course the return NSString value will be equal to "var".

In general, how can I use reflection in Swift in order to obtain, as in this example the name of a argument variable? So far, I've checked numerous resources, starting from the Apple's documentation onto the runtime API and similar, but unfortunately, none of the pre-defined runtime methods seem to be applicable in Swift.

dsafa
  • 783
  • 2
  • 8
  • 29
  • Could you please show us your `ChildElement` class/struct. – dfrib Jan 31 '16 at 14:05
  • Sure! Check out the updated question. Although the structure of the ChildElement class is not relevant to this question. – dsafa Jan 31 '16 at 14:10
  • It's relevant for all questions to present a [minimal, complete and verifiable](http://stackoverflow.com/help/mcve) example, mostly to increase the probability for someone to help you (as some won't bother to try if they need to add custom dummy classes of their own just to reproduce a behaviour someone is looking to try out). – dfrib Jan 31 '16 at 14:42

2 Answers2

1

Swift's reflection features are limited and a bit cumbersome to use so I made a couple of utility functions to simplify them. This is based on the Mirror class (which is read only for now)

Also note that only stored properties can be accessed using Mirror.

func fieldValue(object:AnyObject, _ name:String) -> Any?
{
   let nameComponents   = name.componentsSeparatedByString(".")

   guard let fieldName  = nameComponents.first 
   else { return nil }

   let subFieldName     = nameComponents.dropFirst().joinWithSeparator(".")

   for prop in Mirror(reflecting:object).children
   {
      if let name = prop.label
         where name == fieldName 
      {
         if subFieldName == "" { return prop.value }
         if let subObject = prop.value as? AnyObject
         { return fieldValue(subObject, subFieldName) } 
         return nil           
      }          
   }
   return nil
} 

func fieldNames(object:AnyObject, prefix:String = "") -> [String]
{
   var names:[String] = []
   for prop in Mirror(reflecting:object).children
   {
      if let name = prop.label
      {
         let fieldName = prefix + name
         names.append(fieldName)
         if let subObject = prop.value as? AnyObject
         { names = names + fieldNames(subObject, prefix:fieldName + ".") } 
      }          
   }
   return names
}


class ChildClass
{
   var subProperty:String = "ABC"
}

class ContainerClass
{
   var aProperty:Int       = 123
   var anObject:ChildClass = ChildClass()
}

let foo = ContainerClass()
fieldNames(foo)                        // ["aProperty", "anObject", "anObject.subProperty"]
fieldValue(foo,"aProperty")            // 123
fieldValue(foo,"anObject.subProperty") // "ABC"
Alain T.
  • 40,517
  • 4
  • 31
  • 51
0

I know you only asked for the retrieval of names of properties, but if you also are interested in the type of a property in a certain class - without the requirement of having an instance of said class - I have wrote a project for this.

I have a solution that finds the name and type of a property given any class that inherits from NSObject.

I wrote a lengthy explanation on StackOverflow here, and my project is available here on Github,

In short you can do something like this (but really check out the code Github):

public class func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? {
    var count = UInt32()
    guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
    var types: Dictionary<String, Any> = [:]
    for i in 0..<Int(count) {
        guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
        let type = getTypeOf(property: property)
        types[name] = type
    }
    free(properties)
    return types
}
Community
  • 1
  • 1
Sajjon
  • 8,938
  • 5
  • 60
  • 94