Swift reflection capabilities - how to get an instance variable name?

五迷三道 提交于 2020-06-16 04:34:39

问题


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.


回答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"



回答2:


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
}


来源:https://stackoverflow.com/questions/35114064/swift-reflection-capabilities-how-to-get-an-instance-variable-name

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!