目标-自省/反思

是否有一个内置的方法,函数,API,普遍接受的方式,等等,以转储一个实例化对象的内容在 Objective-C,特别是在苹果的 Cocoa/Cocoa-Touch 环境?

我希望能够做一些像

MyType *the_thing = [[MyType alloc] init];
NSString *the_dump = [the_thing dump]; //pseudo code
NSLog("Dumped Contents: %@", the_dump);

并显示对象的实例变量名称和值,以及运行时可以调用的任何方法。最好是易于阅读的格式。

对于熟悉 PHP 的开发人员,我基本上在寻找与反射函数(var_dump()get_class_methods())和 OO 反射 API 等价的函数。

43590 次浏览

缺少 description方法(如。在 Java 中,我还没有听说过内置的 toString () ,但是创建一个应该不会太难。Objective-C 运行时引用有很多函数可以用来获取关于对象的实例变量、方法、属性等的信息。

更新: 任何想要做这类事情的人可能都想看看 用于 Objective-C 运行时的 Mike Ash 的 OBC 包装器

这或多或少是你会怎么做的:

#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);
}

从这里,很容易得到一个实例的属性的实际值,但是你必须检查它们是否是原始类型或对象,所以我懒得把它放进去。您还可以选择扫描继承链,以获得对象上定义的属性 所有。然后还有定义类别的方法,还有更多... ... 但是几乎所有的东西都是现成的。

下面是上述代码为 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
);
}

下面是我目前正在使用的自动打印类变量的方法,在库中最终公开发布——它的工作原理是将实例类中的所有属性全部转储到继承树中。由于 KVC,您不需要关心属性是否是基元类型(对于大多数类型)。

// 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]]];
}

老实说,这项工作的正确工具是 Xcode 的调试器。所有这些信息都可以通过视觉方式轻松获取。花时间学习如何使用它,这是一个非常强大的工具。

更多信息:

使用调试器

过时的 Xcode 调试指南 -由 Apple 归档

关于使用 Xcode 调试——由 Apple 归档

关于 LLDB 和调试 -由 Apple 存档

使用 GDB 调试-由 Apple 存档

SpriteKit 调试指南 -由 Apple 存档

调试 Core Foundation 的编程主题 -由 Apple 归档

我对 Kendall 打印属性值的代码做了一些调整,这对我来说非常方便。我将它定义为一个实例方法,而不是类方法,因为超类递归就是这样调用它的。我还为不兼容 KVO 的属性添加了异常处理,并在输出中添加了换行符,以便于阅读(和 diff) :

-(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;
}

我已经用这个做出了 cocoapod,https://github.com/neoneye/autodescribe

我已经修改了 Christopher Pickslay 的代码,并将其作为 NSObject 上的一个类别,还为其添加了一个单元测试。以下是使用方法:

@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

我以前对自省和反省感到困惑,所以在下面得到一些信息。

内省是对象检查自己是哪种类型,遵循哪种协议,或者选择自己能够响应的内容的能力。如 isKindOfClass/isMemberOfClass/conformsToProtocol/respondsToSelector等。

反射能力比自省能力更进一步,它不仅可以获取对象信息,而且可以操作对象元数据、属性和函数。例如 object_setClass可以修改对象类型。