2013-05-31 2 views
11

Мне было интересно, можно ли определить класс или примитивный тип свойств объектов. Получение всех свойств имен и значений довольно просто. SO answerтип собственности или класс с использованием отражения

Итак, есть ли способ получить тип класса свойств, в то время как свойство не имеет значения или значения nil?

Пример кода

@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 

ответ

29

После поиска через Apples Documentation about objc Runtime и в соответствии с this SO answer я, наконец, получил это работает. Я просто хочу поделиться своими результатами.

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

Не является ли ваше утверждение if излишним? Если typeAttribute имеет этот префикс, не имеет ли он автоматически длину> 1, так как префикс имеет 2 символа? – rvijay007

+0

Вы правы. Я исправлю это! –

+1

Это очень хорошая вещь, и я рад, что нашел ваш ответ! Это отлично подходит для разбора bullsh * t на классы ObjC, например, когда ребята из API решили отправить все как Strings :(:(:( –

4

Вдохновленный ответ ObjC на @ Арндта-Биберштейн Я написал решение в Swift 3 (вероятно, очень похожи - если не то же самое - в более ранних версиях Swift). Вы можете найти его на Github Я пытаюсь сделать его, но у меня возникают проблемы с получением pob lib lint для работы с кодом Swift 3 (вероятно, CLI xcodebuild или проблема с Xcode 8.) В любом случае метод класса func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? может извлечь имя и типы любого класса Swift, который наследуется от NSObject.

Работа лошадь проекты эти методы, но проверка полного кода на 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 
    } 

может извлечь NSObject.Type все свойства, какой тип класса наследует от NSObject, таких как NSDate (Swift3: Date), NSString (Swift3: String?) И NSNumber, однако это магазин в виде Any (как вы можете видеть, как тип значения словаря, возвращаемого методом). Это связано с ограничениями value types, такими как Int, Int32, Bool. Поскольку эти типы не наследуются от NSObject, вызывая .self напр. Int - Int.self не возвращает NSObject.Type, а тип Any. Таким образом, метод возвращает Dictionary<String, Any>?, а не Dictionary<String, NSObject.Type>?.

Вы можете использовать этот метод, как это:

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' 

Вы также можете попробовать забрасывать Any к NSObject.Type, который сменит для всех свойств, наследующих от NSObject, то вы можете проверить тип с помощью стандартного == оператора :

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

Если объявить этот обычай == оператор:

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

Вы можете даже проверить тип value types как это:

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

ОГРАНИЧЕНИЕ Я до сих пор не были в состоянии дать эту поддержку проекта для того, когда value types являются опциями. Если вы объявили свойство в вашем подклассе NSObject следующим образом: var myOptionalInt: Int? мое решение не будет работать, потому что метод class_copyPropertyList не может найти эти свойства.

У кого-нибудь есть решение для этого?

+0

См. Мой ответ в https://stackoverflow.com/questions/25421312/cant-fetch-optional-variables-with-class-copypropertylist-in-swift/48265855#48265855 I могут найти опционы в Obj-C, используя class_copyPropertyList. Единственное предостережение, которое я получаю, это NSKeyedUnarchiver, похоже, бросает исключение, пытаясь прочитать дополнительный NSNumber blob из CoreData. Регулярный NSNumber работает. Просто не факультативно. –

Смежные вопросы