Objective-C Introspection / Reflection


79

Có phương thức, hàm, API tích hợp sẵn, cách thường được chấp nhận, v.v. để kết xuất nội dung của một đối tượng được khởi tạo trong Objective-C, cụ thể là trong môi trường Cocoa / Cocoa-Touch của Apple không?

Tôi muốn có thể làm một cái gì đó như

MyType *the_thing = [[MyType alloc] init];
NSString *the_dump = [the_thing dump]; //pseudo code
NSLog("Dumped Contents: %@", the_dump);

và hiển thị tên và giá trị biến cá thể của đối tượng, cùng với bất kỳ phương thức nào có sẵn để gọi tại thời điểm chạy. Lý tưởng nhất là ở định dạng dễ đọc.

Đối với các nhà phát triển quen thuộc với PHP, về cơ bản tôi đang tìm kiếm hàm phản chiếu tương đương ( var_dump(), get_class_methods()) và API phản chiếu OO.


Tôi tự hỏi câu hỏi tương tự có vài ngày. Cảm ơn bạn vì câu hỏi này.
Yannick Loriot

7
Vâng, câu hỏi hay. Một trong những lợi thế lớn nhất của ObjC so với các ngôn ngữ tương tự khác là hệ thống thời gian chạy động tuyệt vời của nó cho phép bạn làm những điều tuyệt vời như thế này. Thật không may, mọi người hiếm khi sử dụng nó với tiềm năng đầy đủ của nó, vì vậy các kudo để dạy cộng đồng SO về nó với câu hỏi của bạn.
rpj 20/02/10

Tôi đã tạo một thư viện nhẹ để xử lý OSReflectionKit phản chiếu . Sử dụng thư viện này, bạn có thể đơn giản gọi [the_thing fullDescription].
Alexandre OS

Câu hỏi tuyệt vời!! - Bạn có bất kỳ ý tưởng nào về cách thiết lập thuộc tính thực tế khi bạn đã tìm thấy nó bằng Reflection không? Tôi có một câu hỏi ở đây: stackoverflow.com/q/25538890/1735836
Patricia

Câu trả lời:


112

CẬP NHẬT: Bất kỳ ai muốn thực hiện loại nội dung này đều có thể muốn xem trình bao bọc objC của Mike Ash cho thời gian chạy Objective-C .

Đây ít nhiều là cách bạn thực hiện:

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

Từ đó, thật dễ dàng để lấy các giá trị thực của các thuộc tính của một cá thể, nhưng bạn phải kiểm tra xem chúng có phải là kiểu hay đối tượng nguyên thủy hay không, vì vậy tôi đã quá lười để đưa nó vào. Bạn cũng có thể chọn quét chuỗi kế thừa để lấy tất cả các thuộc tính được xác định trên một đối tượng. Sau đó, có các phương pháp được xác định trên các danh mục, và hơn thế nữa ... Nhưng hầu hết mọi thứ đều có sẵn.

Dưới đây là đoạn trích về những gì đoạn mã trên kết xuất cho 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 đó là khá nhiều cách tôi sẽ làm điều đó (biến nó thành một NSObjectdanh mục). Tuy nhiên, bạn phải free()bạn ivars, propertiesmethodsmảng, nếu không bạn đang bị rò rỉ chúng.
Dave DeLong

Rất tiếc, quên điều đó. Đặt chúng vào ngay bây giờ. Cảm ơn.
Felixyz

4
Chúng ta không thể nhìn thấy giá trị của ivars?
Samhan Salahuddin

2
... Và các loại của chúng. Tôi nhận thấy bạn nói rằng nó có thể được thực hiện, nhưng bạn chắc chắn hiểu rõ hơn về điều này nhiều hơn tôi. Bạn có thể cập nhật điều này vào một lúc nào đó bằng mã để nhận các giá trị và kiểu cho ivars và thuộc tính không?
GeneralMike

Với ARC, có cần phải giải phóng các ivars, thuộc tính và mảng phương thức, v.v. không?
Chuck Krutsinger

12

descriptionNói về phương thức (như .toString () trong Java), tôi chưa nghe nói về một phương thức được tích hợp sẵn, nhưng sẽ không quá khó để tạo một phương thức. Tham chiếu thời gian chạy Objective-C có một loạt các hàm mà bạn có thể sử dụng để lấy thông tin về các biến cá thể, phương thức, thuộc tính, v.v. của đối tượng.


+1 cho thông tin, chỉ là loại điều tôi đang tìm kiếm. Điều đó nói rằng, tôi đã hy vọng vào một giải pháp được xây dựng trước, vì tôi đủ mới với ngôn ngữ mà bất kỳ thứ gì tôi muốn nhanh chóng hack với nhau có thể bỏ sót một số điểm chính của ngôn ngữ.
Alan Storm

7
Nếu bạn định từ chối câu trả lời, vui lòng để lại bình luận tại sao.
Dave DeLong

8

Đây là những gì tôi hiện đang sử dụng để tự động in các biến lớp, trong một thư viện để phát hành công khai cuối cùng - nó hoạt động bằng cách kết xuất tất cả các thuộc tính từ lớp cá thể theo cách sao lưu cây kế thừa. Nhờ KVC, bạn không cần phải quan tâm xem thuộc tính có phải là kiểu nguyên thủy hay không (đối với hầu hết các kiểu).

// 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 rất hữu ích, mặc dù nó chỉ trả lời một phần câu hỏi. Bạn sẽ thêm nhận xét ở đây khi bạn phát hành thư viện chứ?
Felixyz

@KendallHelmstetterGelner - THÔNG TIN TUYỆT VỜI !! Bạn có bất kỳ ý tưởng nào về cách đặt thuộc tính thực tế khi bạn đã tìm thấy nó bằng Reflection không? Tôi có một câu hỏi ở đây: stackoverflow.com/q/25538890/1735836
Patricia

@Lucy, bất kỳ chuỗi nào bạn lấy lại bằng kỹ thuật này đều có thể được sử dụng trong [myObject setValue: myValue forKey: propertyName]. Kỹ thuật tôi đã nêu ở trên (sử dụng danh sách thuộc tính) là phản ánh ... bạn cũng có thể sử dụng mảng objc_property_t để gọi trực tiếp các phương thức thuộc tính, nhưng setValue: forKey: dễ sử dụng hơn và hoạt động tốt.
Kendall Helmstetter Gelner

4

Tôi đã thực hiện một vài chỉnh sửa đối với mã của Kendall để in các giá trị thuộc tính, điều này rất hữu ích cho tôi. Tôi đã định nghĩa nó như một phương thức thể hiện thay vì một phương thức lớp, vì đó là cách đệ quy lớp cha gọi nó. Tôi cũng đã thêm xử lý ngoại lệ cho các thuộc tính không tuân thủ KVO và thêm dấu ngắt dòng vào đầu ra để giúp dễ đọc hơn (và khác biệt):

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

THÔNG TIN TUYỆT VỜI !!! - Bạn có bất kỳ ý tưởng nào về cách thiết lập thuộc tính thực tế khi bạn đã tìm thấy nó bằng Reflection không? Tôi có một câu hỏi ở đây: stackoverflow.com/q/25538890/1735836
Patricia

3

Thành thật mà nói, công cụ phù hợp cho công việc này là trình gỡ lỗi của Xcode. Nó có tất cả thông tin này dễ dàng truy cập một cách trực quan. Hãy dành thời gian để học cách sử dụng nó, nó là một công cụ thực sự mạnh mẽ.

Thêm thông tin:

Sử dụng Trình gỡ lỗi

Hướng dẫn gỡ lỗi Xcode lỗi thời - do Apple lưu trữ

Giới thiệu về Gỡ lỗi bằng Xcode - do Apple lưu trữ

Giới thiệu về LLDB và Gỡ lỗi - do Apple lưu trữ

Gỡ lỗi với GDB - do Apple lưu trữ

Hướng dẫn gỡ lỗi SpriteKit - do Apple lưu trữ

Chủ đề lập trình gỡ lỗi cho Core Foundation - do Apple lưu trữ


4
+1 để có thông tin tốt, nhưng đôi khi việc kết xuất trạng thái của một đối tượng tại hai điểm và khác đầu ra sẽ nhanh hơn so với việc nó bắt đầu ở trạng thái của một chương trình tại một thời điểm.
Alan Storm

1

Tôi đã tạo cocoapod từ điều này, https://github.com/neoneye/autodescribe

Tôi đã sửa đổi mã của Christopher Pickslay và làm cho nó trở thành một danh mục trên NSObject và cũng thêm một mã mới nhất vào nó. Đây là cách sử dụng nó:

@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

Tôi đang nhầm lẫn với Nội tâm và Phản xạ trước đây, vì vậy hãy tìm hiểu một số thông tin dưới đây.

Nội tâm là khả năng đối tượng kiểm tra xem nó thuộc loại nào, hoặc giao thức mà nó tuân thủ, hoặc bộ chọn mà nó có thể phản hồi. Các api objc như isKindOfClass/ isMemberOfClass/ conformsToProtocol/ respondsToSelectorv.v.

Khả năng phản chiếu còn hơn cả Nội quan , Nó không chỉ có thể lấy thông tin về đối tượng mà còn có thể vận hành siêu dữ liệu, thuộc tính và chức năng của đối tượng. chẳng hạn như object_setClasscó thể sửa đổi loại đối tượng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.