【问题标题】:Objective-C Introspection/ReflectionObjective-C 内省/反思
【发布时间】:2011-01-18 23:33:17
【问题描述】:

是否有内置方法、函数、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())和 OO 反射 API 的等效项。

【问题讨论】:

  • 是的,好问题。与其他类似语言相比,ObjC 的最大优势之一是其令人惊叹的动态运行时系统,它允许您做类似这样的很棒的事情。不幸的是,人们很少充分利用它的全部潜力,因此感谢您用您的问题向 SO 社区传授有关它的知识。
  • 我创建了一个轻量级库来处理反射OSReflectionKit。使用这个库,您可以简单地调用 [the_thing fullDescription]。
  • 好问题!! - 您知道如何使用反射找到实际属性后如何设置它吗?我在这里有一个问题:stackoverflow.com/q/25538890/1735836

标签: objective-c cocoa xcode reflection introspection


【解决方案1】:

更新:任何想要做这类事情的人都可能想查看Mike Ash's ObjC wrapper for the 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);
}

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

以下是 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
    );
}

【讨论】:

  • +1 这就是我的做法(将其设为NSObject 类别)。但是,您必须 free() 您的 ivarspropertiesmethods 数组,否则您将泄漏它们。
  • 糟糕,忘记了。现在把它们放进去。谢谢。
  • 我们看不到 ivars 的值吗?
  • ... 以及它们的类型。我注意到你说可以做到,但你肯定比我对此有更好的理解。您能否在某个时候使用代码更新它以获取 ivars 和属性的值和类型?
  • 有了ARC,还需要释放ivars、属性、方法数组等吗?
【解决方案2】:

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

【讨论】:

  • +1 的信息,正是我正在寻找的那种东西。也就是说,我希望有一个预先构建的解决方案,因为我对这门语言还很陌生,所以我很快就会拼凑起来的任何东西都可能会错过该语言的一些主要的微妙之处。
  • 如果您要对答案投反对票,请礼貌地留下评论原因。
【解决方案3】:

这是我目前用于在最终公开发布的库中自动打印类变量的方法 - 它通过将实例类中的所有属性一直转储到继承树上来工作。感谢 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 非常有用,尽管它只回答了部分问题。发布库时会在此处添加评论吗?
  • @KendallHelmstetterGelner - 很棒的信息!!一旦使用反射找到它,您是否知道如何设置实际属性?我在这里有一个问题:stackoverflow.com/q/25538890/1735836
  • @Lucy,您使用此技术返回的任何字符串都可以在 [myObject setValue:myValue forKey:propertyName] 中使用。我上面概述的技术(使用属性列表)是反射...您也可以使用 objc_property_t 数组直接调用属性方法,但是 setValue:forKey: 更易于使用并且效果也很好。
【解决方案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;
}

【讨论】:

【解决方案5】:

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

更多信息:

Using the Debugger

Outdated Xcode Debugging Guide - Apple 存档

About Debugging with Xcode - Apple 存档

About LLDB and Debugging - Apple 存档

Debugging with GDB - Apple 存档

SpriteKit Debugging Guide - Apple 存档

Debugging Programming Topics for Core Foundation - Apple 存档

【讨论】:

  • +1 以获得更好的信息,但有时在两个点转储对象的状态并区分输出比在单个时间点从程序状态开始更快。
【解决方案6】:

我用这个做了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

【讨论】:

    【解决方案7】:

    我之前对自省和反思感到困惑,所以请在下面获取一些信息。

    自省是对象检查它是哪种类型、它符合的协议或它可以响应的选择器的能力。 objc api如isKindOfClass/isMemberOfClass/conformsToProtocol/respondsToSelector等。

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

    【讨论】:

      猜你喜欢
      • 2014-10-01
      • 2018-11-26
      • 2011-01-03
      • 1970-01-01
      • 2012-02-23
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 2011-09-09
      相关资源
      最近更新 更多