Objective-C 검사 / 반사


79

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에 해당하는 것을 찾고 있습니다.


며칠 동안 같은 질문이 있는지 궁금합니다. 이 질문에 감사드립니다.
Yannick Loriot

7
네, 좋은 질문입니다. 다른 유사한 언어에 비해 ObjC의 가장 큰 장점 중 하나는 이와 같은 멋진 작업을 수행 할 수있는 놀라운 동적 런타임 시스템입니다. 불행히도 사람들은 그것을 최대한 활용하는 경우가 드물기 때문에 질문에 대해 SO 커뮤니티에 가르치는 것에 찬사를 보냅니다.
rpj 2010

리플렉션 OSReflectionKit 처리를위한 경량 라이브러리를 만들었습니다 . 이 라이브러리를 사용하여 [the_thing fullDescription]을 간단히 호출 할 수 있습니다.
Alexandre OS

좋은 질문 !! -Reflection을 사용하여 찾은 실제 속성을 설정하는 방법을 알고 있습니까? 여기에 질문이 있습니다 : stackoverflow.com/q/25538890/1735836
Patricia

답변:


112

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

6
+1 그게 제가하는 방식입니다 ( NSObject카테고리로 만드세요 ). 그러나, 당신은에있는 free()당신 ivars, propertiesmethods배열, 그렇지 않으면 당신이 그들을 누출하고 있습니다.
Dave DeLong

웁스, 잊어 버렸습니다. 지금 넣으십시오. 감사.
Felixyz

4
ivar의 값을 볼 수 없습니까?
Samhan Salahuddin

2
... 그리고 그들의 유형. 나는 당신이 할 수 있다고 말했다는 것을 알아 챘지만, 당신은 분명히 이것에 대해 나보다 훨씬 더 잘 이해하고있는 것 같습니다. ivar 및 속성에 대한 값과 유형을 가져 오는 코드로 어느 시점에서 이것을 업데이트 할 수 있습니까?
GeneralMike 2013

ARC를 사용하면 ivar, 속성 및 메서드 배열 등을 해제해야합니까?
Chuck Krutsinger 2014 년

12

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


정보에 +1, 내가 찾던 종류의 것입니다. 즉, 내가 빠르게 함께 해킹하는 모든 것이 언어의 주요 미묘한 부분을 놓칠 수있을만큼 언어에 익숙하기 때문에 사전 구축 된 솔루션을 바라고있었습니다.
Alan 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-좋은 정보 !! Reflection을 사용하여 찾은 실제 속성을 설정하는 방법을 알고 있습니까? 여기에 질문이 있습니다 : stackoverflow.com/q/25538890/1735836
Patricia

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

훌륭한 정보 !!! -Reflection을 사용하여 찾은 실제 속성을 설정하는 방법을 알고 있습니까? 여기에 질문이 있습니다 : stackoverflow.com/q/25538890/1735836
Patricia

3

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

추가 정보:

디버거 사용

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

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

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

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

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

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


4
좋은 정보를 얻으려면 +1하지만 때로는 두 지점에서 객체의 상태를 덤프하고 단일 시점의 프로그램 상태에서 시작하는 것보다 출력을 비교하는 것이 더 빠릅니다.
Alan Storm

1

나는 이것으로 코코아 포드를 만들었습니다, 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

0

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.