[objective-c] 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 리플렉션 API에 해당하는 것을 찾고 있습니다.



답변

업데이트 : 이런 종류의 작업을 원하는 사람 은 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);
}

여기에서 인스턴스 속성의 실제 값을 쉽게 얻을 수 있지만 기본 유형인지 객체인지 확인해야하므로 입력하기에는 너무 게으르다. 상속 체인을 스캔하도록 선택할 수도 있습니다. 객체에 정의 된 모든 속성을 가져옵니다 . 그런 다음 범주에 대해 정의 된 방법이 있습니다. 그러나 거의 모든 것을 쉽게 사용할 수 있습니다.

다음은 위 코드가 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
    );
}


답변

의 짧은 description(자바로 .toString 같은 ()) 방법은, 내가 년에 지어진 하나 들어하지 않은,하지만 하나를 만들 너무 어렵지 않을 것입니다. Objective-C 런타임 참조 에는 개체의 인스턴스 변수, 메서드, 속성 등에 대한 정보를 가져 오는 데 사용할 수있는 여러 함수가 있습니다.


답변

다음은 최종 공개 릴리스를 위해 라이브러리에서 클래스 변수를 자동으로 인쇄하기 위해 현재 사용하고 있습니다. 상속 트리를 백업하는 모든 방법으로 인스턴스 클래스의 모든 속성을 덤프하여 작동합니다. 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]]];
}


답변

속성 값을 인쇄하기위한 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;
}


답변

솔직히이 작업에 적합한 도구는 Xcode의 디버거입니다. 이 모든 정보를 시각적으로 쉽게 액세스 할 수 있습니다. 시간을내어 사용법을 배우십시오. 정말 강력한 도구입니다.

추가 정보:

디버거 사용

오래된 Xcode 디버깅 가이드 -Apple에서 보관함

Xcode를 사용한 디버깅에 관하여 -Apple에서 보관함

LLDB 및 디버깅에 관하여 -Apple에서 보관함

GDB로 디버깅 -Apple에서 보관함

SpriteKit 디버깅 가이드 – 애플 보관

Core Foundation을위한 프로그래밍 주제 디버깅 -Apple에서 보관함


답변

나는 이것으로 코코아 포드를 만들었습니다, https://github.com/neoneye/autodescribe

Christopher Pickslay의 코드를 수정하고 NSObject의 카테고리로 만들고 unittest도 추가했습니다. 사용 방법은 다음과 같습니다.

@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


답변

이전에는 Introspection 및 Refection과 혼동이 있으므로 아래 정보를 확인하십시오.

Introspection은 객체가 어떤 유형인지 확인하거나 준수한 프로토콜 또는 응답 할 수있는 선택기를 확인하는 기능입니다. isKindOfClass/ isMemberOfClass/ conformsToProtocol/ respondsToSelector등과 같은 objc API

Refection 기능은 Introspection 이상이며 , 객체 정보를 얻을 수있을뿐만 아니라 객체 메타 데이터, 속성 및 기능을 조작 할 수도 있습니다. 등은 object_setClass개체 유형을 수정할 수 있습니다.