web-dev-qa-db-ja.com

Objective-Cイントロスペクション/反射

Objective-C、具体的にはAppleのCocoa/Cocoa-Touch環境でインスタンス化されたオブジェクトのコンテンツをダンプするための組み込みのメソッド、関数、API、一般に受け入れられている方法などはありますか?

次のようなことができるようになりたい

_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 Reflection API)に相当するものを探しています。

78
Alan Storm

UPDATE:この種のことをしたい人は誰でもチェックアウトしたいと思うかもしれません Objective-Cランタイム用のMike AshのObjCラッパー

これは多かれ少なかれあなたがそれについて行くだろう方法です:

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

そこから、インスタンスのプロパティの実際の値を取得するのは簡単ですが、それらがプリミティブ型またはオブジェクトであるかどうかを確認する必要があるため、私はそれを入れるのが面倒でした。また、継承チェーンをスキャンしてgetallオブジェクトで定義されたプロパティ。次に、カテゴリで定義されたメソッドなどがあります...しかし、ほとんどすべてがすぐに利用できます。

上記のコードが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
    );
}
112
Felixyz

descriptionメソッド(Javaの.toString()など)が不足しているため、組み込まれたものは聞いていませんが、作成するのはそれほど難しくありません。 Objective-Cランタイムリファレンス には、オブジェクトのインスタンス変数、メソッド、プロパティなどに関する情報を取得するために使用できる一連の関数があります。

12
Dave DeLong

最終的にパブリックリリースのライブラリで、クラス変数を自動的に出力するために現在使用しているものは次のとおりです。インスタンスクラスからすべてのプロパティを継承ツリーの最後までダンプすることで機能します。 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]]];
}

プロパティ値を印刷するためにケンドールのコードをいくつか調整しました。これは非常に便利です。それをクラスメソッドではなくインスタンスメソッドとして定義しました。スーパークラスの再帰がそれを呼び出す方法だからです。また、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;
}

正直なところ、この仕事に適したツールはXcodeのデバッガーです。これらの情報はすべて視覚的に簡単にアクセスできます。それを使用する方法を学ぶために時間をかけて、それは本当に強力なツールです。

詳しくは:

デバッガーの使用

旧式のXcodeデバッグガイド -Appleによりアーカイブ

Xcodeを使用したデバッグについて -Appleによりアーカイブ

LLDBとデバッグについて -Appleによりアーカイブ

GDBを使用したデバッグ -Appleによりアーカイブ

SpriteKitデバッグガイド -Appleによりアーカイブ

Core Foundationのプログラミングトピックのデバッグ -Appleによってアーカイブされました

3
Colin Barrett

これからココアポッドを作りました 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
1
neoneye

私は以前、内観と宗教と混同されていたので、以下の情報を入手してください。

イントロスペクションは、オブジェクトがどのタイプであるか、準拠しているプロトコル、または応答できるセレクターをチェックする機能です。 isKindOfClass/isMemberOfClass/conformsToProtocol/respondsToSelectorなどのオブジェクトAPI.

Refection機能はIntrospectionよりも優れています。オブジェクト情報を取得できるだけでなく、オブジェクトのメタデータ、プロパティ、関数を操作することもできます。といった object_setClassはオブジェクトタイプを変更できます。

0
Jone