Nhận danh sách thuộc tính đối tượng trong Objective-C


109

Làm cách nào để có được danh sách (dưới dạng một NSArrayhoặc NSDictionary) các thuộc tính đối tượng nhất định trong Objective-C?

Hãy tưởng tượng tình huống sau: Tôi đã định nghĩa một lớp cha vừa mở rộng NSObject, chứa một NSString, a BOOLvà một NSDatađối tượng làm thuộc tính. Sau đó, tôi có một số lớp mở rộng lớp cha này, thêm nhiều thuộc tính khác nhau mỗi lớp.

Có cách nào tôi có thể thực hiện một phương pháp dụ trên mẹ lớp mà đi qua toàn bộ đối tượng và trả về, nói rằng, một NSArraycủa mỗi (trẻ em) thuộc tính lớp như NSStringsrằng là không trên lớp cha mẹ, vì vậy tôi sau này có thể sử dụng các NSStringcho KVC?

Câu trả lời:


116

Tôi chỉ có thể tự mình có được câu trả lời. Bằng cách sử dụng Thư viện thời gian chạy Obj-C, tôi có quyền truy cập vào các thuộc tính theo cách tôi muốn:

- (void)myMethod {
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for(i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithCString:propName
                                                                encoding:[NSString defaultCStringEncoding]];
            NSString *propertyType = [NSString stringWithCString:propType
                                                                encoding:[NSString defaultCStringEncoding]];
            ...
        }
    }
    free(properties);
}

Điều này yêu cầu tôi phải tạo một hàm 'getPropertyType' C, hàm này chủ yếu được lấy từ một mẫu mã của Apple (hiện không thể nhớ chính xác nguồn):

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T') {
            if (strlen(attribute) <= 4) {
                break;
            }
            return (const char *)[[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];
        }
    }
    return "@";
}

5
+1 ngoại trừ điều này sẽ lỗi trên các số nguyên thủy, chẳng hạn như int. Vui lòng xem câu trả lời của tôi bên dưới để biết phiên bản nâng cao hơn một chút của điều tương tự.
jpswain 21/12/11

1
Như một vấn đề về tính đúng đắn, [NSString stringWithCString:]không được ủng hộ [NSString stringWithCString:encoding:].
zekel

4
Nên nhập tiêu đề thời gian chạy objc #import <objc / runtime.h> Nó hoạt động trên ARC.
Dae KIM

Đây là cách thực hiện nó bằng Swift.
Ramis

76

Câu trả lời của @ boliva là tốt, nhưng cần thêm một chút để xử lý các nguyên thủy, như int, long, float, double, v.v.

Tôi đã xây dựng của anh ấy để thêm chức năng này.

// PropertyUtil.h
#import 

@interface PropertyUtil : NSObject

+ (NSDictionary *)classPropsFor:(Class)klass;

@end


// PropertyUtil.m
#import "PropertyUtil.h"
#import "objc/runtime.h"

@implementation PropertyUtil

static const char * getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    printf("attributes=%s\n", attributes);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // it's a C primitive type:
            /* 
                if you want a list of what will be returned for these primitives, search online for
                "objective-c" "Property Attribute Description Examples"
                apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc.            
            */
            return (const char *)[[NSData dataWithBytes:(attribute + 1) length:strlen(attribute) - 1] bytes];
        }        
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // it's an ObjC id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // it's another ObjC object type:
            return (const char *)[[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];
        }
    }
    return "";
}


+ (NSDictionary *)classPropsFor:(Class)klass
{    
    if (klass == NULL) {
        return nil;
    }

    NSMutableDictionary *results = [[[NSMutableDictionary alloc] init] autorelease];

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            NSString *propertyType = [NSString stringWithUTF8String:propType];
            [results setObject:propertyType forKey:propertyName];
        }
    }
    free(properties);

    // returning a copy here to make sure the dictionary is immutable
    return [NSDictionary dictionaryWithDictionary:results];
}




@end

1
Bạn có định có #import <Foundation/Foundation.h>ở đầu tệp .h không?
Andrew

2
[NSString stringWithUTF8String: propType] không thể phân tích cú pháp "propType const char *" NSNumber \ x94 \ xfdk; "và trả về một chuỗi nil ... Không biết tại sao nó lại là một
Số NSN

Tuyệt vời! Cảm ơn rất nhiều.
Azik Abdullah

Điều này là hoàn toàn hoàn hảo!
Pranoy C

28

Câu trả lời của @ orange80 có một vấn đề: Nó thực sự không phải lúc nào cũng kết thúc chuỗi bằng số 0. Điều này có thể dẫn đến kết quả không mong muốn như gặp sự cố trong khi cố gắng chuyển đổi nó sang UTF8 (Tôi thực sự đã gặp phải một lỗi khá khó chịu chỉ vì điều đó. Rất vui khi gỡ lỗi nó ^^). Tôi đã sửa nó bằng cách thực sự lấy một NSString từ thuộc tính và sau đó gọi cStringUsingEncoding :. Điều này làm việc như một sự quyến rũ bây giờ. (Cũng hoạt động với ARC, ít nhất là đối với tôi)

Vì vậy, đây là phiên bản mã của tôi bây giờ:

// PropertyUtil.h
#import 

@interface PropertyUtil : NSObject

+ (NSDictionary *)classPropsFor:(Class)klass;

@end


// PropertyUtil.m
#import "PropertyUtil.h"
#import <objc/runtime.h>

@implementation PropertyUtil

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    //printf("attributes=%s\n", attributes);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // it's a C primitive type:
            /*
             if you want a list of what will be returned for these primitives, search online for
             "objective-c" "Property Attribute Description Examples"
             apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc.
             */
            NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // it's an ObjC id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // it's another ObjC object type:
            NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
    }
    return "";
}


+ (NSDictionary *)classPropsFor:(Class)klass
{
    if (klass == NULL) {
        return nil;
    }

    NSMutableDictionary *results = [[NSMutableDictionary alloc] init];

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            NSString *propertyType = [NSString stringWithUTF8String:propType];
            [results setObject:propertyType forKey:propertyName];
        }
    }
    free(properties);

    // returning a copy here to make sure the dictionary is immutable
    return [NSDictionary dictionaryWithDictionary:results];
}

@end

@farthen bạn có thể cung cấp một ví dụ minh họa sự cố với mã mà tôi đã cung cấp không? Tôi chỉ tò mò muốn xem nó.
jpswain

@ cam80 Chà, AFAIR dữ liệu không bao giờ là số 0 cả. Nếu đó là điều này chỉ xảy ra một cách tình cờ. Tôi có thể đã sai. Trong các tin tức khác: Tôi vẫn có mã này chạy và nó chạy đá rắn: p
felinira

@ orange80 Tôi gặp sự cố này khi cố gọi phiên bản của bạn trên IMAAdRequest từ thư viện quảng cáo IMA của google. giải pháp của xì hơi đã giải quyết nó.
Christopher Pickslay

Cảm ơn. Điều này làm việc với tôi trong iOS7 khi hai câu trả lời trước đó không. +1 cho tất cả 3.
ChrisH

Đây là câu trả lời duy nhất phù hợp với tôi. Mọi thứ khác đã đem lại cho tôi như "NSString \ x8d \ xc0 \ xd9" weirdness cho các loại tài sản, có lẽ vì các char * kích thước đã được tắt
Brian Colavito

8

Khi tôi thử với iOS 3.2, chức năng getPropertyType không hoạt động tốt với mô tả thuộc tính. Tôi đã tìm thấy một ví dụ từ tài liệu iOS: "Hướng dẫn lập trình Objective-C Runtime: Thuộc tính đã khai báo".

Đây là mã sửa đổi cho danh sách tài sản trong iOS 3.2:

#import <objc/runtime.h>
#import <Foundation/Foundation.h>
...
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([UITouch class], &outCount);
for(i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
free(properties);

7

Tôi nhận thấy rằng giải pháp của boliva hoạt động tốt trong trình mô phỏng, nhưng trên thiết bị, chuỗi con có độ dài cố định gây ra sự cố. Tôi đã viết một giải pháp thân thiện hơn với Objective-C cho vấn đề này hoạt động trên thiết bị. Trong phiên bản của tôi, tôi chuyển đổi C-String của các thuộc tính thành một NSString và thực hiện các thao tác với chuỗi trên nó để nhận được một chuỗi con chỉ mô tả kiểu.

/*
 * @returns A string describing the type of the property
*/

+ (NSString *)propertyTypeStringOfProperty:(objc_property_t) property {
    const char *attr = property_getAttributes(property);
    NSString *const attributes = [NSString stringWithCString:attr encoding:NSUTF8StringEncoding];

    NSRange const typeRangeStart = [attributes rangeOfString:@"T@\""];  // start of type string
    if (typeRangeStart.location != NSNotFound) {
        NSString *const typeStringWithQuote = [attributes substringFromIndex:typeRangeStart.location + typeRangeStart.length];
        NSRange const typeRangeEnd = [typeStringWithQuote rangeOfString:@"\""]; // end of type string
        if (typeRangeEnd.location != NSNotFound) {
            NSString *const typeString = [typeStringWithQuote substringToIndex:typeRangeEnd.location];
            return typeString;
        }
    }
    return nil;
}

/**
* @returns (NSString) Dictionary of property name --> type
*/

+ (NSDictionary *)propertyTypeDictionaryOfClass:(Class)klass {
    NSMutableDictionary *propertyMap = [NSMutableDictionary dictionary];
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for(i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {

            NSString *propertyName = [NSString stringWithCString:propName encoding:NSUTF8StringEncoding];
            NSString *propertyType = [self propertyTypeStringOfProperty:property];
            [propertyMap setValue:propertyType forKey:propertyName];
        }
    }
    free(properties);
    return propertyMap;
}

Đây ném một ngoại lệ EXC_BAD_ACCESS trên NSRange const typeRangeStart = [thuộc tính rangeOfString: @ "T @ \" "]; // bắt đầu kiểu string
Adam Mendoza

6

Việc triển khai này hoạt động với cả kiểu đối tượng Objective-C và C nguyên thủy. Nó tương thích với iOS 8. Lớp này cung cấp ba phương thức lớp:

+ (NSDictionary *) propertiesOfObject:(id)object;

Trả về một từ điển của tất cả các thuộc tính hiển thị của một đối tượng, bao gồm cả những thuộc tính từ tất cả các lớp cha của nó.

+ (NSDictionary *) propertiesOfClass:(Class)class;

Trả về một từ điển của tất cả các thuộc tính hiển thị của một lớp, bao gồm các thuộc tính từ tất cả các lớp cha của nó.

+ (NSDictionary *) propertiesOfSubclass:(Class)class;

Trả về một từ điển của tất cả các thuộc tính hiển thị dành riêng cho một lớp con. Thuộc tính cho các lớp cha của nó không được bao gồm.

Một ví dụ hữu ích về việc sử dụng các phương thức này là sao chép một đối tượng vào một cá thể lớp con trong Objective-C mà không cần phải chỉ định các thuộc tính trong một phương thức sao chép . Các phần của câu trả lời này dựa trên các câu trả lời khác cho câu hỏi này nhưng nó cung cấp một giao diện rõ ràng hơn cho các chức năng mong muốn.

Tiêu đề:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

@interface SYNUtilities : NSObject
+ (NSDictionary *) propertiesOfObject:(id)object;
+ (NSDictionary *) propertiesOfClass:(Class)class;
+ (NSDictionary *) propertiesOfSubclass:(Class)class;
@end

Thực hiện:

//  SYNUtilities.m

#import "SYNUtilities.h"
#import <objc/objc-runtime.h>

@implementation SYNUtilities
+ (NSDictionary *) propertiesOfObject:(id)object
{
    Class class = [object class];
    return [self propertiesOfClass:class];
}

+ (NSDictionary *) propertiesOfClass:(Class)class
{
    NSMutableDictionary * properties = [NSMutableDictionary dictionary];
    [self propertiesForHierarchyOfClass:class onDictionary:properties];
    return [NSDictionary dictionaryWithDictionary:properties];
}

+ (NSDictionary *) propertiesOfSubclass:(Class)class
{
    if (class == NULL) {
        return nil;
    }

    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    return [self propertiesForSubclass:class onDictionary:properties];
}

+ (NSMutableDictionary *)propertiesForHierarchyOfClass:(Class)class onDictionary:(NSMutableDictionary *)properties
{
    if (class == NULL) {
        return nil;
    }

    if (class == [NSObject class]) {
        // On reaching the NSObject base class, return all properties collected.
        return properties;
    }

    // Collect properties from the current class.
    [self propertiesForSubclass:class onDictionary:properties];

    // Collect properties from the superclass.
    return [self propertiesForHierarchyOfClass:[class superclass] onDictionary:properties];
}

+ (NSMutableDictionary *) propertiesForSubclass:(Class)class onDictionary:(NSMutableDictionary *)properties
{
    unsigned int outCount, i;
    objc_property_t *objcProperties = class_copyPropertyList(class, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = objcProperties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            NSString *propertyType = [NSString stringWithUTF8String:propType];
            [properties setObject:propertyType forKey:propertyName];
        }
    }
    free(objcProperties);

    return properties;
}

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // A C primitive type:
            /*
             For example, int "i", long "l", unsigned "I", struct.
             Apple docs list plenty of examples of values returned. For a list
             of what will be returned for these primitives, search online for
             "Objective-c" "Property Attribute Description Examples"
             */
            NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // An Objective C id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // Another Objective C id type:
            NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
    }
    return "";
}

@end

Tôi nhận được một ngoại lệ EXC_BAD_ACCESS trên dòng này NSString * name = [[NSString CẤP] initWithBytes: thuộc tính + 1 length: strlen (thuộc tính) - 1 mã hóa: NSASCIIStringEncoding];
Adam Mendoza

4

Nếu ai đó đang cần lấy cũng như các thuộc tính được thừa kế từ các lớp cha (như tôi đã làm) thì đây là một số sửa đổi trên mã " orange80 " để làm cho nó đệ quy:

+ (NSDictionary *)classPropsForClassHierarchy:(Class)klass onDictionary:(NSMutableDictionary *)results
{
    if (klass == NULL) {
        return nil;
    }

    //stop if we reach the NSObject class as is the base class
    if (klass == [NSObject class]) {
        return [NSDictionary dictionaryWithDictionary:results];
    }
    else{

        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList(klass, &outCount);
        for (i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            const char *propName = property_getName(property);
            if(propName) {
                const char *propType = getPropertyType(property);
                NSString *propertyName = [NSString stringWithUTF8String:propName];
                NSString *propertyType = [NSString stringWithUTF8String:propType];
                [results setObject:propertyType forKey:propertyName];
            }
        }
        free(properties);

        //go for the superclass
        return [PropertyUtil classPropsForClassHierarchy:[klass superclass] onDictionary:results];

    }
}

1
Chúng ta không thể đặt đây là một danh mục và mở rộng NSObject với nó để chức năng này được tích hợp vào mọi lớp là con của NSObject?
Alex Zavatone 12/1213

Nghe có vẻ là một ý kiến ​​hay, nếu tôi có thể tìm thấy thời gian sẽ cập nhật câu trả lời với tùy chọn đó.
PakitoV 13/1213

Khi bạn đã hoàn tất việc đó, tôi sẽ thêm một phương thức kết xuất khi có thời gian. Đã đến lúc chúng ta có thuộc tính đối tượng thực và phần nội quan của phương pháp trên mỗi NSObject.
Alex Zavatone

Tôi cũng đang làm việc để thêm đầu ra giá trị, nhưng có vẻ như đối với một số cấu trúc (phiên bản), kiểu là giá trị thực của thuộc tính. Đây là trường hợp xảy ra với caretRect của một tableViewController và các int không được đánh dấu khác trong một cấu trúc viewController trả về c hoặc f là kiểu xung đột với tài liệu Thời gian chạy mục tiêu-C. Rõ ràng là cần phải làm việc nhiều hơn ở đây để hoàn thành việc này. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone

Tôi đã xem xét nhưng có một vấn đề mà tôi không thể giải quyết, để làm cho nó đệ quy, tôi cần gọi phương thức cho lớp cha (như trong dòng cuối cùng của mã trước đó) vì NSObject là lớp gốc sẽ không hoạt động bên trong một danh mục . Vì vậy, không đệ quy có thể ... :( không chắc chắn nếu một danh mục trong NSObject là con đường để đi nữa ...
PakitoV

3

Từ "thuộc tính" hơi mờ. Ý của bạn là các biến cá thể, thuộc tính, phương thức trông giống như trình truy cập?

Câu trả lời cho cả ba là "có, nhưng nó không phải là rất dễ dàng." Các runtime API Objective-C bao gồm các chức năng để có được danh sách Ivar, danh sách phương pháp hoặc danh sách bất động sản cho một lớp học (ví dụ, class_copyPropertyList()), và sau đó một chức năng tương ứng đối với từng loại để có được tên của một mục trong danh sách (ví dụ property_getName()).

Nói chung, có thể phải làm rất nhiều việc để làm cho nó đúng, hoặc ít nhất là nhiều hơn những gì mà hầu hết mọi người muốn làm cho những gì thường chỉ là một tính năng thực sự tầm thường.

Ngoài ra, bạn có thể chỉ cần viết một đoạn mã Ruby / Python mà chỉ cần đọc một tệp tiêu đề và tìm kiếm bất cứ thứ gì bạn coi là "thuộc tính" cho lớp.


Xin chào, cảm ơn vì phản hồi của bạn. Những gì tôi đề cập đến với 'thuộc tính' thực sự là thuộc tính của một lớp. Tôi đã quản lý để đạt được những gì tôi muốn bằng cách sử dụng Thư viện thời gian chạy Obj-C. Sử dụng tập lệnh để phân tích cú pháp tệp tiêu đề sẽ không hoạt động cho những gì tôi cần trong thời gian chạy.
boliva

3

Tôi đã có thể nhận được câu trả lời của @ cam80 là làm việc VỚI ARC ĐƯỢC BẬT … ... cho những gì tôi muốn - ít nhất là ... nhưng không phải là không có một chút thử và sai. Hy vọng rằng thông tin bổ sung này có thể giúp ai đó bớt đau buồn.

Lưu những lớp mà anh ấy mô tả trong câu trả lời của mình = dưới dạng một lớp, và đặt trong AppDelegate.h(hoặc bất cứ thứ gì) của bạn #import PropertyUtil.h. Sau đó, trong ...

- (void)applicationDidFinishLaunching:
         (NSNotification *)aNotification {

phương pháp (hoặc bất cứ điều gì)

PropertyUtil *props  = [PropertyUtil new];  
NSDictionary *propsD = [PropertyUtil classPropsFor:
                          (NSObject*)[gist class]];  
NSLog(@"%@, %@", props, propsD);

Bí mật là ép kiểu biến cá thể của lớp bạn ( trong trường hợp này là lớp của tôi Gist, và trường hợp của tôi Gistgist ) mà bạn muốn truy vấn ... tới NSObject ... (id), v.v., sẽ không cắt nó .. vì nhiều thứ khác nhau, kỳ lạ , lý do bí truyền. Điều này sẽ cung cấp cho bạn một số đầu ra như vậy…

<PropertyUtil: 0x7ff0ea92fd90>, {
apiURL = NSURL;
createdAt = NSDate;
files = NSArray;
gistDescription = NSString;
gistId = NSString;
gitPullURL = NSURL;
gitPushURL = NSURL;
htmlURL = NSURL;
isFork = c;
isPublic = c;
numberOfComments = Q;
updatedAt = NSDate;
userLogin = NSString;
}

Đối với tất cả những điều không hề nao núng của Apple / OCD khi khoe khoang về "những điều ngạc nhiên" "về nội tâm" của objC ... Họ chắc chắn không dễ dàng thực hiện "cái nhìn" "về bản thân", "có thể nói" đơn giản này ..

Nếu bạn thực sự muốn đi hoang dã .. hãy kiểm tra .. class-dump , đó là một cách điên rồ để xem qua các tiêu đề lớp của BẤT KỲ tệp thực thi nào, v.v. Nó cung cấp một cái nhìn ĐỘNG TỪ vào các lớp của bạn… mà tôi, cá nhân, thấy thực sự hữu ích - trong rất nhiều trường hợp. nó thực sự là lý do tại sao tôi bắt đầu tìm kiếm một giải pháp cho câu hỏi của OP. đây là một số thông số sử dụng .. thưởng thức!

    -a             show instance variable offsets
    -A             show implementation addresses
    --arch <arch>  choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64)
    -C <regex>     only display classes matching regular expression
    -f <str>       find string in method name
    -I             sort classes, categories, and protocols by inheritance (overrides -s)
    -r             recursively expand frameworks and fixed VM shared libraries
    -s             sort classes and categories by name
    -S             sort methods by name

3

Bạn có ba phép thuật

Ivar* ivars = class_copyIvarList(clazz, &count); // to get all iVars
objc_property_t  *properties = class_copyPropertyList(clazz, &count); //to get all properties of a class 
Method* methods = class_copyMethodList(clazz, &count); // to get all methods of a class.

Đoạn mã sau có thể giúp bạn.

-(void) displayClassInfo
{
    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* classInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                           ivarArray, @"ivars",
                           propertyArray, @"properties",
                           methodArray, @"methods",
                           nil];

        NSLog(@"%@", classInfo);
}

2

Tôi đang sử dụng hàm boliva được cung cấp, nhưng rõ ràng là nó đã ngừng hoạt động với iOS 7. Vì vậy, bây giờ thay vì static const char * getPropertyType (thuộc tính objc_property_t), người ta có thể sử dụng như sau:

- (NSString*) classOfProperty:(NSString*)propName{

objc_property_t prop = class_getProperty([self class], [propName UTF8String]);
if (!prop) {
    // doesn't exist for object
    return nil;
}
const char * propAttr = property_getAttributes(prop);
NSString *propString = [NSString stringWithUTF8String:propAttr];
NSArray *attrArray = [propString componentsSeparatedByString:@","];
NSString *class=[attrArray objectAtIndex:0];
return [[class stringByReplacingOccurrencesOfString:@"\"" withString:@""] stringByReplacingOccurrencesOfString:@"T@" withString:@""];
}

Bạn là anh hùng của tôi. Tôi vẫn phải sửa một số thứ theo cách thủ công (Vì một số lý do BOOLs sắp được gọi là 'Tc'), nhưng điều này thực sự cho phép tôi làm cho mọi thứ hoạt động trở lại.
Harpastum,

Nguyên thủy có kiểu riêng, "@" biểu thị các đối tượng và sau nó, tên lớp xuất hiện giữa các dấu ngoặc kép. Ngoại lệ duy nhất là id đó được mã hóa đơn giản là "T @"
Mihai Timar

2

Đối với người xem Swift, bạn có thể có được chức năng này bằng cách sử dụng Encodablechức năng. Tôi sẽ giải thích cách:

  1. Biến đổi đối tượng của bạn thành Encodablegiao thức

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Tạo tiện ích mở rộng Encodableđể cung cấp toDictionarychức năng

     public func toDictionary() -> [String: AnyObject]? {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        guard let data =  try? encoder.encode(self),
              let json = try? JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0)), let jsonDict = json as? [String: AnyObject] else {
            return nil
        }
        return jsonDict
    }
  3. Gọi toDictionaryđối tượng của bạn và truy cập thuộc keystính.

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. Thì đấy! Truy cập các thuộc tính của bạn như vậy:

    for k in exampleObj!.keys {
        print(k)
    }
    // Prints "prop1"
    // Prints "prop2"

1

Những câu trả lời này hữu ích, nhưng tôi yêu cầu nhiều hơn từ đó. Tất cả những gì tôi muốn làm là kiểm tra xem loại lớp của một thuộc tính có bằng với loại của một đối tượng hiện có hay không. Tất cả các mã trên không có khả năng làm như vậy, bởi vì: Để lấy tên lớp của một đối tượng, object_getClassName () trả về các văn bản như sau:

__NSArrayI (for an NSArray instance)
__NSArrayM (for an NSMutableArray instance)
__NSCFBoolean (an NSNumber object initialized by initWithBool:)
__NSCFNumber (an NSValue object initialized by [NSNumber initWithBool:])

Nhưng nếu gọi getPropertyType (...) từ mã mẫu trên, với 4 cấu trúc objc_property_t của các thuộc tính của một lớp được định nghĩa như thế này:

@property (nonatomic, strong) NSArray* a0;
@property (nonatomic, strong) NSArray* a1;
@property (nonatomic, copy) NSNumber* n0;
@property (nonatomic, copy) NSValue* n1;

nó trả về các chuỗi tương ứng như sau:

NSArray
NSArray
NSNumber
NSValue

Vì vậy, không thể xác định liệu một NSObject có khả năng là giá trị của một thuộc tính của lớp hay không. Làm thế nào để làm điều đó sau đó?

Đây là mã mẫu đầy đủ của tôi (hàm getPropertyType (...) giống như ở trên):

#import <objc/runtime.h>

@interface FOO : NSObject

@property (nonatomic, strong) NSArray* a0;
@property (nonatomic, strong) NSArray* a1;
@property (nonatomic, copy) NSNumber* n0;
@property (nonatomic, copy) NSValue* n1;

@end

@implementation FOO

@synthesize a0;
@synthesize a1;
@synthesize n0;
@synthesize n1;

@end

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    //printf("attributes=%s\n", attributes);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // it's a C primitive type:

            // if you want a list of what will be returned for these primitives, search online for
            // "objective-c" "Property Attribute Description Examples"
            // apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc.

            NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // it's an ObjC id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // it's another ObjC object type:
            NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
    }
    return "";
}

int main(int argc, char * argv[]) {
    NSArray* a0 = [[NSArray alloc] init];
    NSMutableArray* a1 = [[NSMutableArray alloc] init];
    NSNumber* n0 = [[NSNumber alloc] initWithBool:YES];
    NSValue* n1 = [[NSNumber alloc] initWithBool:NO];
    const char* type0 = object_getClassName(a0);
    const char* type1 = object_getClassName(a1);
    const char* type2 = object_getClassName(n0);
    const char* type3 = object_getClassName(n1);

    objc_property_t property0 = class_getProperty(FOO.class, "a0");
    objc_property_t property1 = class_getProperty(FOO.class, "a1");
    objc_property_t property2 = class_getProperty(FOO.class, "n0");
    objc_property_t property3 = class_getProperty(FOO.class, "n1");
    const char * memberthype0 = getPropertyType(property0);//property_getAttributes(property0);
    const char * memberthype1 = getPropertyType(property1);//property_getAttributes(property1);
    const char * memberthype2 = getPropertyType(property2);//property_getAttributes(property0);
    const char * memberthype3 = getPropertyType(property3);//property_getAttributes(property1);
    NSLog(@"%s", type0);
    NSLog(@"%s", type1);
    NSLog(@"%s", type2);
    NSLog(@"%s", type3);
    NSLog(@"%s", memberthype0);
    NSLog(@"%s", memberthype1);
    NSLog(@"%s", memberthype2);
    NSLog(@"%s", memberthype3);

    return 0;
}
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.