Objective-C 内省/反射

78

有没有一种内置的方法、函数、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的等效物。


7
没错,好问题。Objective-C相较于其他类似语言的最大优势之一就是它惊人的动态运行时系统,使你可以实现像这样的很棒的功能。不幸的是,人们很少能够完全发挥其潜力,因此感谢你通过提问向Stack Overflow社区传授相关知识。 - rpj
我创建了一个轻量级的反射库OSReflectionKit。使用这个库,你可以简单地调用[the_thing fullDescription]。 - Alexandre OS
非常好的问题!!您有没有想过如何在使用反射找到属性后设置实际属性?我在这里有一个问题:https://dev59.com/w4Lba4cB1Zd3GeqPkeHz - Patricia
7个回答

111

更新:任何想要执行此类操作的人都可以查看Mike Ash 的ObjC封装 Objective-C runtime

以下是你要执行的步骤:

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

从那里开始,获取实例属性的实际值很容易,但您必须检查它们是原始类型还是对象,所以我懒得把它放进去。您也可以选择扫描继承链以获取对象上定义的所有属性。 然后还有在类别上定义的方法等等……但几乎所有东西都是readily available。

以下是上述代码转储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 类别)。然而,你需要释放你的 ivarspropertiesmethods 数组,否则你就会泄露它们。 - Dave DeLong
4
我们能否查看ivars的值? - Samhan Salahuddin
2
...以及它们的类型。我注意到你说这可以做到,但是你似乎比我更理解这个。你能否更新一下代码,获取ivars和properties的值和类型? - GeneralMike
使用ARC,是否仍然需要释放ivars、properties、methods数组等? - Chuck Krutsinger
@Felixyz - 这太棒了!!!你有没有想法在使用反射找到属性后如何设置实际属性?我在这里提出了一个问题:https://dev59.com/w4Lba4cB1Zd3GeqPkeHz - Patricia
显示剩余2条评论

12

除了像Java中的.toString()方法一样的description方法外,我还没有听说过内置的方法,但创建一个不会太困难。您可以使用Objective-C Runtime参考中提供的一系列函数来获取有关对象实例变量、方法、属性等信息。


+1 对这个信息表示赞同,正是我所需要的。不过,我希望能够找到一个预先构建好的解决方案,因为我对这门语言还不够熟悉,自己草率地编写可能会忽略掉一些重要的细节。 - Alana Storm
7
如果您要对一个答案进行负评,请有礼貌地留下评论说明原因。 - Dave DeLong

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
@KendallHelmstetterGelner - 非常好的信息!您有没有想过如何在使用反射找到属性后设置实际属性?我在这里有一个问题:https://dev59.com/w4Lba4cB1Zd3GeqPkeHz - Patricia
@Lucy,使用此技术返回的任何字符串都可以用于[myObject setValue:myValue forKey:propertyName]。我上面概述的技术(使用属性列表)是反射...您也可以使用objc_property_t数组直接调用属性方法,但setValue:forKey:更易于使用且同样有效。 - Kendall Helmstetter Gelner

4

我对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;
}

非常棒的信息!!! - 你有没有想过如何在使用反射找到属性后设置实际属性值?我在这里有一个问题:https://dev59.com/w4Lba4cB1Zd3GeqPkeHz - Patricia

3

4
好信息要点赞,但有时候比起从单个时间点开始程序状态,记录对象在两个时间点的状态并对输出进行差异分析更快。 - Alana Storm

1

我已经基于这个代码库创建了CocoaPods,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等。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接