2013-05-31 5 views
11

Mi chiedevo se è possibile determinare la classe o il tipo primitivo di proprietà di un oggetto. Ottenere tutti i nomi e i valori delle proprietà è piuttosto semplice. SO answertipo di proprietà o classe utilizzando la riflessione

Quindi esiste un modo per ottenere il tipo di classe proprietà mentre la proprietà non ha valore o valore nullo?

Esempio di codice

@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 

risposta

29

Dopo la ricerca attraverso Apples Documentation about objc Runtime e secondo this SO answer ho finalmente capito di lavoro. Voglio solo condividere i miei risultati.

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:@"[email protected]"]) { 
     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); 
+0

La dichiarazione if non è ridondante? Se typeAttribute ha quel prefisso, non ha automaticamente una lunghezza> 1 dato che il prefisso è di 2 caratteri? – rvijay007

+0

Hai ragione. Lo aggiusterò! –

+1

Questa è una cosa molto carina da sapere, e sono contento di aver trovato la tua risposta! Questo è assolutamente ottimo per analizzare bullsh * t nelle classi ObjC, ad esempio quando i ragazzi dell'API hanno deciso di inviare tutto come stringhe :(:(:( –

4

Ispirato dalla risposta objC da @ Arndt-Bieberstein Ho scritto una soluzione in Swift 3 (probabilmente molto simile - se non stessi - nelle versioni precedenti di Swift). Lo trovo su Github Sto cercando di farne un pod ma sto avendo problemi con pob lib lint per lavorare con il codice Swift 3 (probabilmente il problema relativo a CLI xcodebuild o Xcode 8). In ogni caso, il metodo di classe func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? può estrarre il nome e tipi di qualsiasi classe Swift che eredita da NSObject.

Il cavallo lavoro del progetto sono questi metodi, ma checkout il codice completo su Github:

func getTypesOfProperties(in 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 
    } 

    func getTypeOf(property: objc_property_t) -> 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 
    } 

E 'possibile estrarre il NSObject.Type di tutte le proprietà che tipo di classe eredita dalla NSObject come NSDate (Swift3: Date), NSString (Swift3: String?) E NSNumber, tuttavia è memorizzato nel tipo Any (come è possibile vedere come il tipo di valore del dizionario restituito dal metodo). Ciò è dovuto alle limitazioni di value types come Int, Int32, Bool. Poiché questi tipi non ereditano da NSObject, chiamando .self su es. un Int - Int.self non restituisce NSObject.Type, ma il tipo Any. Pertanto, il metodo restituisce Dictionary<String, Any>? e non Dictionary<String, NSObject.Type>?.

È possibile utilizzare questo metodo come questo:

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' 

Potete anche provare a lanciare il Any-NSObject.Type, che avrà successo per tutte le proprietà che ereditano da NSObject, allora si può verificare il tipo utilizzando == operatore di serie :

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'") 
      } 
     } 
    } 
} 

Se si dichiara questa usanza == operatore:

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

È quindi possibile anche verificare il tipo di value types come questo:

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'") 
     } 
    } 
} 

LIMITAZIONI perché non sono ancora stati in grado di dare a questo progetto di sostegno per quando il value types sono optional. Se hai dichiarato una proprietà nella tua sottoclasse NSObject come questa: var myOptionalInt: Int? la mia soluzione non funzionerà, perché il metodo class_copyPropertyList non riesce a trovare quelle proprietà.

Qualcuno ha una soluzione per questo?

+0

Vedere la mia risposta in https://stackoverflow.com/questions/25421312/cant-fetch-optional-variables-with-class-copypropertylist-in-swift/48265855#48265855 I può trovare le opzioni in Obj-C usando class_copyPropertyList. Solo l'avvertenza che sto ottenendo è che NSKeyedUnarchiver sembra lanciare un'eccezione nel tentativo di rileggere un blob NSNumber opzionale da CoreData. Funziona un normale NSNumber. Solo non un optional. –