tipo de propiedad o clase usando reflexión

Me preguntaba si es posible determinar el tipo de clase o primitivo de las propiedades de un Objeto. Obtener todos los nombres y valores de las propiedades es bastante fácil. Pues contesta

Entonces, ¿hay alguna forma de obtener el tipo de clase de propiedades mientras que la propiedad no tiene valor o valor nulo?

Código de ejemplo

@interface MyObject : NSObject @property (nonatomic, copy) NSString *aString; @property (nonatomic, copy) NSDate *aDate; @property NSInteger aPrimitive; @end @implementation MyObject @synthesize aString; @synthesize aDate; @synthesize aPrimitive; - (void)getTheTypesOfMyProperties { unsigned int count; objc_property_t* props = class_copyPropertyList([self class], &count); for (int i = 0; i < count; i++) { objc_property_t property = props[i]; // Here I can easy get the name or value const char * name = property_getName(property); // But is there any magic function that can tell me the type? // the property can be nil at this time Class cls = magicFunction(property); } free(props); } @end 

Después de buscar en la documentación de Apples sobre objc Runtime y de acuerdo con esta respuesta de SO finalmente lo conseguí trabajando. Solo quiero compartir mis resultados.

 unsigned int count; objc_property_t* props = class_copyPropertyList([MyObject class], &count); for (int i = 0; i < count; i++) { objc_property_t property = props[i]; const char * name = property_getName(property); NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; const char * type = property_getAttributes(property); NSString *attr = [NSString stringWithCString:type encoding:NSUTF8StringEncoding]; NSString * typeString = [NSString stringWithUTF8String:type]; NSArray * attributes = [typeString componentsSeparatedByString:@","]; NSString * typeAttribute = [attributes objectAtIndex:0]; NSString * propertyType = [typeAttribute substringFromIndex:1]; const char * rawPropertyType = [propertyType UTF8String]; if (strcmp(rawPropertyType, @encode(float)) == 0) { //it's a float } else if (strcmp(rawPropertyType, @encode(int)) == 0) { //it's an int } else if (strcmp(rawPropertyType, @encode(id)) == 0) { //it's some sort of object } else { // According to Apples Documentation you can determine the corresponding encoding values } if ([typeAttribute hasPrefix:@"T@"]) { NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns @"NSDate" into NSDate Class typeClass = NSClassFromString(typeClassName); if (typeClass != nil) { // Here is the corresponding class even for nil values } } } free(props); 

Inspirado por la respuesta ObjC de @arndt-bieberstein, escribí una solución en Swift 3 (probablemente muy similar, si no igual) en versiones anteriores de Swift. Puedes encontrarlo en Github Estoy tratando de crear un pod, pero tengo problemas para que pob lib lint funcione con el código Swift 3 (CLI xcodebuild o el problema relacionado con Xcode 8, probablemente). De todos modos, el método de clase func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary? puede extraer el nombre y los tipos de cualquier clase Swift que herede de NSObject .

El caballo de batalla del proyecto son estos métodos, pero revisa el código completo en Github :

  func getTypesOfProperties(in clazz: NSObject.Type) -> Dictionary? { var count = UInt32() guard let properties = class_copyPropertyList(clazz, &count) else { return nil } var types: Dictionary = [:] for i in 0.. Any { guard let attributesAsNSString: NSString = NSString(utf8String: property_getAttributes(property)) else { return Any.self } let attributes = attributesAsNSString as String let slices = attributes.components(separatedBy: "\"") guard slices.count > 1 else { return getPrimitiveDataType(withAttributes: attributes) } let objectClassName = slices[1] let objectClass = NSClassFromString(objectClassName) as! NSObject.Type return objectClass } func getPrimitiveDataType(withAttributes attributes: String) -> Any { guard let letter = attributes.substring(from: 1, to: 2), let type = primitiveDataTypes[letter] else { return Any.self } return type } func getNameOf(property: objc_property_t) -> String? { guard let name: NSString = NSString(utf8String: property_getName(property)) else { return nil } return name as String } 

Puede extraer el NSObject.Type de todas las propiedades que tipo de clase hereda de NSObject como NSDate (Swift3: Date ), NSString (Swift3: String ?) Y NSNumber , sin embargo, se almacena en el tipo Any (como se puede ver como tipo del valor del Diccionario devuelto por el método). Esto se debe a las limitaciones de los value types de value types como Int, Int32, Bool. Como esos tipos no heredan de NSObject, llamar a .self en, por ejemplo, un Int – Int.self no devuelve NSObject.Type, sino más bien el tipo Any . Por lo tanto, el método devuelve Dictionary? y no Dictionary? .

Puedes usar este método así:

 class Book: NSObject { let title: String let author: String? let numberOfPages: Int let released: Date let isPocket: Bool init(title: String, author: String?, numberOfPages: Int, released: Date, isPocket: Bool) { self.title = title self.author = author self.numberOfPages = numberOfPages self.released = released self.isPocket = isPocket } } guard let types = getTypesOfProperties(inClass: Book.self) else { return } for (name, type) in types { print("'\(name)' has type '\(type)'") } // Prints: // 'title' has type 'NSString' // 'numberOfPages' has type 'Int' // 'author' has type 'NSString' // 'released' has type 'NSDate' // 'isPocket' has type 'Bool' 

También puede tratar de convertir Any to NSObject.Type , que tendrá éxito para todas las propiedades NSObject de NSObject , luego puede verificar el tipo usando el operador == estándar.

 func checkPropertiesOfBook() { guard let types = getTypesOfProperties(inClass: Book.self) else { return } for (name, type) in types { if let objectType = type as? NSObject.Type { if objectType == NSDate.self { print("Property named '\(name)' has type 'NSDate'") } else if objectType == NSString.self { print("Property named '\(name)' has type 'NSString'") } } } } 

Si declara este operador personalizado == :

 func ==(rhs: Any, lhs: Any) -> Bool { let rhsType: String = "\(rhs)" let lhsType: String = "\(lhs)" let same = rhsType == lhsType return same } 

Entonces, incluso puede verificar el tipo de value types de value types como este:

 func checkPropertiesOfBook() { guard let types = getTypesOfProperties(inClass: Book.self) else { return } for (name, type) in types { if type == Int.self { print("Property named '\(name)' has type 'Int'") } else if type == Bool.self { print("Property named '\(name)' has type 'Bool'") } } } 

LIMITACIONES Aún no he podido dar soporte a este proyecto para cuando los value types sean opcionales. Si ha declarado una propiedad en su subclase NSObject de esta manera: var myOptionalInt: Int? mi solución no funcionará, porque el método class_copyPropertyList no puede encontrar esas propiedades.

¿Alguien tiene una solución para esto?