目标C反思/反思
有没有一个内置的方法,函数,API,普遍接受的方式等来转储Objective C中的实例化对象的内容,特别是在Apple的Cocoa / Cocoa-Touch环境中?
我希望能够做到这样的事情
MyType *the_thing = [[MyType alloc] init]; NSString *the_dump = [the_thing dump]; //pseudo code NSLog("Dumped Contents: %@", the_dump);
并显示对象的实例variables名称和值,以及可在运行时调用的任何方法。 理想情况下,一个易于阅读的格式。
对于熟悉PHP的开发人员,我基本上是在寻找等价的reflection函数( var_dump()
, get_class_methods()
)和OO Reflection API。
更新:任何想要做这种东西的人可能都想看看Mike Ash的ObjC封装器的Objective-C运行时 。
这或多或less是你如何去做的:
#import <objc/runtime.h> . . . -(void)dumpInfo { Class clazz = [self class]; u_int count; Ivar* ivars = class_copyIvarList(clazz, &count); NSMutableArray* ivarArray = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count ; i++) { const char* ivarName = ivar_getName(ivars[i]); [ivarArray addObject:[NSString stringWithCString:ivarName encoding:NSUTF8StringEncoding]]; } free(ivars); objc_property_t* properties = class_copyPropertyList(clazz, &count); NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count ; i++) { const char* propertyName = property_getName(properties[i]); [propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]]; } free(properties); Method* methods = class_copyMethodList(clazz, &count); NSMutableArray* methodArray = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count ; i++) { SEL selector = method_getName(methods[i]); const char* methodName = sel_getName(selector); [methodArray addObject:[NSString stringWithCString:methodName encoding:NSUTF8StringEncoding]]; } free(methods); NSDictionary* classDump = [NSDictionary dictionaryWithObjectsAndKeys: ivarArray, @"ivars", propertyArray, @"properties", methodArray, @"methods", nil]; NSLog(@"%@", classDump); }
从那里,很容易得到实例的属性的实际值,但是你必须检查它们是否是原始types或对象,所以我懒得把它放进去。你也可以select扫描inheritance链来获取对象上定义的所有属性。 然后有在类别上定义的方法,以及更多…但几乎所有的东西都是可用的。
以下是上述代码转储给UILabel的摘录:
{ ivars = ( "_size", "_text", "_color", "_highlightedColor", "_shadowColor", "_font", "_shadowOffset", "_minFontSize", "_actualFontSize", "_numberOfLines", "_lastLineBaseline", "_lineSpacing", "_textLabelFlags" ); methods = ( rawSize, "setRawSize:", "drawContentsInRect:", "textRectForBounds:", "textSizeForWidth:", . . . ); properties = ( text, font, textColor, shadowColor, shadowOffset, textAlignment, lineBreakMode, highlightedTextColor, highlighted, enabled, numberOfLines, adjustsFontSizeToFitWidth, minimumFontSize, baselineAdjustment, "_lastLineBaseline", lineSpacing, userInteractionEnabled ); }
在description
方法(比如Java中的.toString())方面,我还没有听说过内build的方法,但创build一个方法并不困难。 Objective-C运行时引用有一堆函数可以用来获取关于对象的实例variables,方法,属性等的信息。
这是我目前使用的自动打印类variables,最终公开发布的库中 – 它的工作原理是从实例类中转储所有属性,一直备份到inheritance树。 感谢KVC,你不需要关心一个属性是否是一个原始types(对于大多数types)。
// Finds all properties of an object, and prints each one out as part of a string describing the class. + (NSString *) autoDescribe:(id)instance classType:(Class)classType { NSUInteger count; objc_property_t *propList = class_copyPropertyList(classType, &count); NSMutableString *propPrint = [NSMutableString string]; for ( int i = 0; i < count; i++ ) { objc_property_t property = propList[i]; const char *propName = property_getName(property); NSString *propNameString =[NSString stringWithCString:propName encoding:NSASCIIStringEncoding]; if(propName) { id value = [instance valueForKey:propNameString]; [propPrint appendString:[NSString stringWithFormat:@"%@=%@ ; ", propNameString, value]]; } } free(propList); // Now see if we need to map any superclasses as well. Class superClass = class_getSuperclass( classType ); if ( superClass != nil && ! [superClass isEqual:[NSObject class]] ) { NSString *superString = [self autoDescribe:instance classType:superClass]; [propPrint appendString:superString]; } return propPrint; } + (NSString *) autoDescribe:(id)instance { NSString *headerString = [NSString stringWithFormat:@"%@:%p:: ",[instance class], instance]; return [headerString stringByAppendingString:[self autoDescribe:instance classType:[instance class]]]; }
我对Kendall的打印属性值的代码做了一些调整,这对我来说非常方便。 我将它定义为一个实例方法而不是类方法,因为这是超类recursion调用它的方式。 我还为非KVO兼容的属性添加了exception处理,并在输出中添加了换行符,以便于阅读(和差异):
-(NSString *) autoDescribe:(id)instance classType:(Class)classType { NSUInteger count; objc_property_t *propList = class_copyPropertyList(classType, &count); NSMutableString *propPrint = [NSMutableString string]; for ( int i = 0; i < count; i++ ) { objc_property_t property = propList[i]; const char *propName = property_getName(property); NSString *propNameString =[NSString stringWithCString:propName encoding:NSASCIIStringEncoding]; if(propName) { @try { id value = [instance valueForKey:propNameString]; [propPrint appendString:[NSString stringWithFormat:@"%@=%@\n", propNameString, value]]; } @catch (NSException *exception) { [propPrint appendString:[NSString stringWithFormat:@"Can't get value for property %@ through KVO\n", propNameString]]; } } } free(propList); // Now see if we need to map any superclasses as well. Class superClass = class_getSuperclass( classType ); if ( superClass != nil && ! [superClass isEqual:[NSObject class]] ) { NSString *superString = [self autoDescribe:instance classType:superClass]; [propPrint appendString:superString]; } return propPrint; }
老实说,这个工作的正确工具是Xcode的debugging器。 它具有所有这些信息以一种可视化的方式很容易获得。 花时间学习如何使用它,这是一个非常强大的工具。 更多信息: Xcodedebugging指南 。
我已经做出了这个cocoapod, https://github.com/neoneye/autodescribe
我修改了Christopher Pickslay的代码,并将其作为NSObject的一个类别,并为其添加了unit testing。 以下是如何使用它:
@interface TestPerson : NSObject @property (nonatomic, strong) NSString *firstName; @property (nonatomic, strong) NSString *lastName; @property (nonatomic, strong) NSNumber *age; @end @implementation TestPerson // empty @end @implementation NSObject_AutoDescribeTests -(void)test0 { TestPerson *person = [TestPerson new]; person.firstName = @"John"; person.lastName = @"Doe"; person.age = [NSNumber numberWithFloat:33.33]; NSString *actual = [person autoDescribe]; NSString *expected = @"firstName=John\nlastName=Doe\nage=33.33"; STAssertEqualObjects(actual, expected, nil); } @end