物镜自省/反思


79

是否有内置的方法,函数,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);

并显示对象的实例变量名称和值,以及在运行时可调用的任何方法。理想情况下为易于阅读的格式。

对于熟悉PHP开发人员,我基本上找的反射功能等效(var_dump()get_class_methods())和面向对象的反射API。


我想知道同样的问题有几天。谢谢你这个问题。
Yannick Loriot 2010年

7
是的,很好的问题。ObjC与其他类似语言相比最大的优势之一就是其惊人的动态运行时系统,该系统使您可以执行类似这样的出色工作。不幸的是,人们很少充分利用它的潜力,因此在向SO社区讲解有关您的问题时非常荣幸。
rpj 2010年

我创建了一个轻量级的库来处理反射OSReflectionKit。使用此库,您可以简单地调用[the_thing fullDescription]。
Alexandre OS

伟大的问题!-您是否知道在使用反射找到实际属性后如何设置它?我在这里有一个问题: stackoverflow.com/q/25538890/1735836
Patricia 2014年

Answers:


112

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

这或多或少是您要做的事情:

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

6
+1差不多就是我要做的(将其NSObject分类)。但是,你要free()你的ivarspropertiesmethods数组,否则你将其泄露。
Dave DeLong 2010年

糟糕,忘记了。现在把它们放进去。谢谢。
Felixyz'2

4
无法看到ivars的价值吗?
Samhan Salahuddin 2012年

2
...及其类型。我注意到您说可以做到,但是您肯定比我更了解此事。您是否可以使用代码在某个时候更新它以获得ivars和属性的值和类型?
GeneralMike

使用ARC,是否仍然需要释放ivars,属性和方法数组等?
Chuck Krutsinger 2014年

12

description缺少该方法(类似于Java中的.toString()),我还没有听说过内置的方法,但是创建一个方法并不难。 《 Objective-C运行时参考》提供了许多函数,可用于获取有关对象的实例变量,方法,属性等的信息。


+1以获取信息,这正是我一直在寻找的东西。就是说,我希望有一个预先构建的解决方案,因为我对这种语言还很陌生,所以我很快一起学习的任何东西都可能会错过一些主要的语言。
艾伦·斯托姆

7
如果您不赞成某个答案,请有礼貌地发表评论。
戴夫·德隆

8

这是我当前用于自动打印类变量的方法,该类在最终公开发布的库中-它的工作方式是将实例类的所有属性转储到继承树中。多亏了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]]];
}

+1非常有用,尽管它只能部分回答问题。发布库时,您会在这里添加评论吗?
Felixyz 2010年

@KendallHelmstetterGelner-很棒的信息!您是否知道在使用反射找到实际属性后如何设置它?我在这里有一个问题: stackoverflow.com/q/25538890/1735836
Patricia 2014年

@Lucy,使用此技术获取的任何字符串都可以在[myObject setValue:myValue forKey:propertyName]中使用。我上面概述的技术(使用属性列表)是反射式的...您也可以使用objc_property_t数组直接调用属性方法,但是setValue:forKey:更易于使用并且也可以正常工作。
Kendall Helmstetter Gelner 2014年

4

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

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

伟大的信息!-您是否知道在使用反射找到实际属性后如何设置它?我在这里有一个问题: stackoverflow.com/q/25538890/1735836
Patricia

3

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

更多信息:

使用调试器

过时的Xcode调试指南-Apple存档

关于使用Xcode进行调试-Apple存档

关于LLDB和调试-Apple存档

使用GDB进行调试-Apple存档

SpriteKit调试指南-Apple存档

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


4
+1可以获取良好的信息,但是有时在两个时间点转储对象的状态并比较输出要快于在单个时间点从程序状态开始时的速度。
艾伦·斯托姆

1

我已经用它制作了可可足,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

0

我以前对内省和感染感到困惑,因此请在下面获取一些信息。

内省是对象检查其类型,符合的协议或响应的选择器的能力。所述objc API诸如isKindOfClass/ isMemberOfClass/ conformsToProtocol/respondsToSelector

Refection功能比Introspection还要强,它不仅可以获取对象信息,还可以操作对象元数据,属性和功能。如object_setClass可以修改对象类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.