รับรายการคุณสมบัติของวัตถุใน Objective-C


109

ฉันจะรับรายการ (ในรูปแบบของNSArrayหรือNSDictionary) ของคุณสมบัติวัตถุที่กำหนดใน Objective-C ได้อย่างไร

ลองนึกภาพสถานการณ์ต่อไปนี้: ฉันได้กำหนดคลาสพาเรนต์ที่เพิ่งขยายNSObjectซึ่งถือ an NSString, a BOOLและNSDataobject เป็นคุณสมบัติ จากนั้นฉันมีหลายคลาสที่ขยายคลาสพาเรนต์นี้โดยเพิ่มคุณสมบัติที่แตกต่างกันมากมายในแต่ละคลาส

มีวิธีใด ๆ ที่ฉันสามารถใช้วิธีการเช่นในผู้ปกครองชั้นเรียนที่จะต้องผ่านวัตถุทั้งหมดและผลตอบแทนการพูดการNSArrayของแต่ละ (เด็ก) คุณสมบัติชั้นเป็นNSStringsที่มีไม่ได้ในระดับผู้ปกครองดังนั้นฉันหลังจากนั้นสามารถใช้เหล่านี้NSStringสำหรับ KVC?

คำตอบ:


116

ฉันเพิ่งจะได้รับคำตอบด้วยตัวเอง ด้วยการใช้ Obj-C Runtime Library ฉันสามารถเข้าถึงคุณสมบัติในแบบที่ฉันต้องการ:

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

สิ่งนี้ทำให้ฉันต้องสร้างฟังก์ชัน C 'getPropertyType' ซึ่งส่วนใหญ่นำมาจากตัวอย่างโค้ดของ Apple (ตอนนี้จำแหล่งที่มาที่แน่นอนไม่ได้):

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 ยกเว้นสิ่งนี้จะเกิดข้อผิดพลาดบนพื้นฐานเช่น int โปรดดูคำตอบของฉันด้านล่างสำหรับเวอร์ชันปรับปรุงเล็กน้อยของสิ่งเดียวกันนี้
jpswain

1
เป็นเรื่องของความถูกต้องที่จะเลิกในความโปรดปรานของ[NSString stringWithCString:] [NSString stringWithCString:encoding:]
zekel

4
ควรอิมพอร์ตส่วนหัวรันไทม์ objc #import <objc / runtime.h> ทำงานบน ARC
Dae KIM

นี่คือวิธีบรรลุผลโดยใช้ Swift
Ramis

76

คำตอบของ @ boliva นั้นดี แต่ต้องการความพิเศษเล็กน้อยในการจัดการกับ primitives เช่น int, long, float, double และอื่น ๆ

ฉันสร้างขึ้นจากเขาเพื่อเพิ่มฟังก์ชันนี้

// 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
คุณตั้งใจให้#import <Foundation/Foundation.h>อยู่ด้านบนสุดของไฟล์. h หรือไม่?
Andrew

2
[NSString stringWithUTF8String: propType] ไม่สามารถแยกวิเคราะห์ "propType const char *" NSNumber \ x94 \ xfdk; "และส่งคืนสตริงศูนย์ ... ไม่รู้ว่าทำไมจึงเป็น NSNumber แปลก ๆ Mb เพราะ ActiveRecord?
Dumoko

สุดยอด! ขอบคุณมาก.
Azik Abdullah

สมบูรณ์แบบที่สุด!
Pranoy C

28

คำตอบของ @ orange80 มีปัญหาอย่างหนึ่ง: จริงๆแล้วมันไม่ได้ยุติสตริงด้วย 0 เสมอไป สิ่งนี้อาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดเช่นการขัดข้องในขณะที่พยายามแปลงเป็น UTF8 (จริงๆแล้วฉันมีข้อผิดพลาดที่ค่อนข้างน่ารำคาญเพียงเพราะเหตุนั้นการดีบักมันก็สนุกดีนะ ^^) ฉันแก้ไขโดยรับ NSString จากแอตทริบิวต์จากนั้นเรียก cStringUsingEncoding: ตอนนี้ใช้งานได้เหมือนมีเสน่ห์ (ยังใช้งานได้กับ ARC อย่างน้อยสำหรับฉัน)

นี่คือรหัสเวอร์ชันของฉันตอนนี้:

// 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

@fart จากนั้นคุณสามารถให้ตัวอย่างที่แสดงให้เห็นถึงปัญหาเกี่ยวกับรหัสที่ฉันให้มาได้หรือไม่? ฉันแค่อยากรู้อยากเห็น
jpswain

@ orange80 เอาละ AFAIR ข้อมูลจะไม่มีการสิ้นสุดเป็นศูนย์เลย หากเป็นเช่นนี้เกิดขึ้นโดยบังเอิญเท่านั้น ฉันอาจจะผิด ในข่าวอื่น ๆ : ฉันยังมีรหัสนี้ทำงานอยู่และมันก็ทำงานได้ดี: p
felinira

@ orange80 ฉันพบปัญหานี้โดยพยายามเรียกใช้เวอร์ชันของคุณใน IMAAdRequest จากไลบรารีโฆษณา IMA ของ Google วิธีแก้ปัญหาของ farthen ก็แก้ไขได้
Christopher Pickslay

ขอบคุณ. สิ่งนี้ใช้ได้ผลสำหรับฉันใน iOS7 เมื่อสองคำตอบก่อนหน้านี้ไม่ได้ +1 สำหรับ 3 ทั้งหมด
ChrisH

นี่เป็นคำตอบเดียวที่เหมาะกับฉัน อย่างอื่นให้ฉันเหมือน "NSString \ x8d \ xc0 \ xd9" ความแปลกสำหรับประเภทคุณสมบัติน่าจะเป็นเพราะขนาดถ่าน * ปิดอยู่
Brian Colavito

8

เมื่อฉันลองใช้ iOS 3.2 ฟังก์ชัน getPropertyType ทำงานได้ไม่ดีกับคำอธิบายคุณสมบัติ ฉันพบตัวอย่างจากเอกสาร iOS: "Objective-C Runtime Programming Guide: Declared Properties"

นี่คือรหัสที่แก้ไขสำหรับรายการอสังหาริมทรัพย์ใน 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

ฉันพบว่าโซลูชันของโบลิวาทำงานได้ดีในเครื่องจำลอง แต่บนอุปกรณ์สตริงย่อยที่มีความยาวคงที่ทำให้เกิดปัญหา ฉันได้เขียนวิธีแก้ปัญหา Objective-C-friendly สำหรับปัญหานี้ที่ใช้ได้กับอุปกรณ์แล้ว ในเวอร์ชันของฉันฉันแปลง C-String ของแอตทริบิวต์เป็น NSString และดำเนินการกับสตริงเพื่อรับสตริงย่อยของคำอธิบายประเภทเท่านั้น

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

สิ่งนี้ทำให้เกิดข้อยกเว้น EXC_BAD_ACCESS ใน NSRange const typeRangeStart = [แอตทริบิวต์ rangeOfString: @ "T @ \" "]; // start of type string
Adam Mendoza

6

การใช้งานนี้ใช้ได้กับทั้ง Objective-C object types และ C primitives เข้ากันได้กับ iOS 8 คลาสนี้มีวิธีคลาสสามแบบ:

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

ส่งคืนพจนานุกรมของคุณสมบัติที่มองเห็นได้ทั้งหมดของอ็อบเจ็กต์รวมถึงคุณสมบัติจากคลาสซูเปอร์คลาสทั้งหมด

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

ส่งคืนพจนานุกรมของคุณสมบัติที่มองเห็นได้ทั้งหมดของคลาสรวมถึงคุณสมบัติจากคลาสซูเปอร์คลาสทั้งหมด

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

ส่งคืนพจนานุกรมของคุณสมบัติที่มองเห็นได้ทั้งหมดที่เฉพาะเจาะจงสำหรับคลาสย่อย ไม่รวมคุณสมบัติของ superclasses

ตัวอย่างหนึ่งที่มีประโยชน์ในการใช้วิธีการเหล่านี้คือการคัดลอกวัตถุที่จะอินสแตนซ์คลาสย่อยใน Objective-C โดยไม่ต้องระบุคุณสมบัติในวิธีการคัดลอก บางส่วนของคำตอบนี้อ้างอิงจากคำตอบอื่น ๆ ของคำถามนี้ แต่จะมีอินเทอร์เฟซที่สะอาดกว่าสำหรับฟังก์ชันที่ต้องการ

หัวข้อ:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

การนำไปใช้:

//  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

ฉันได้รับข้อยกเว้น EXC_BAD_ACCESS ในบรรทัดนี้ NSString * name = [[NSString submit] initWithBytes: attribute + 1 length: strlen (attribute) - 1 encoding: NSASCIIStringEncoding];
Adam Mendoza

4

หากใครบางคนต้องการได้รับคุณสมบัติที่สืบทอดมาจากคลาสพาเรนต์ (อย่างที่ฉันทำ) นี่คือการปรับเปลี่ยนบางอย่างในโค้ด " orange80 " เพื่อทำให้เกิดซ้ำ:

+ (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
เราไม่สามารถทำให้หมวดหมู่นี้เป็นหมวดหมู่และขยาย NSObject ได้หรือไม่ดังนั้นฟังก์ชันนี้จึงถูกสร้างขึ้นในทุกชั้นเรียนที่เป็นลูกของ NSObject?
Alex Zavatone

ฟังดูเหมือนเป็นความคิดที่ดีถ้าฉันหาเวลาได้จะอัปเดตคำตอบด้วยตัวเลือกนั้น
PakitoV

เมื่อเสร็จแล้วฉันจะเพิ่มเมธอดดัมพ์เมื่อมีเวลา ถึงเวลาที่เรามีคุณสมบัติของวัตถุจริงและวิธีการวิปัสสนาอยู่เหนือ NSObject ทุกอัน
Alex Zavatone

ฉันได้ทำการเพิ่มเอาท์พุทค่าเช่นกัน แต่ดูเหมือนว่าสำหรับโครงสร้างบางส่วน (rects) ประเภทคือมูลค่าที่แท้จริงของคุณสมบัติ กรณีนี้เกิดขึ้นกับ caretRect ของ tableViewController และ ints อื่น ๆ ที่ไม่ได้ลงชื่อในโครงสร้าง viewController จะส่งคืน c หรือ f เป็นประเภทที่ขัดแย้งกับเอกสาร Object-C Runtime จำเป็นต้องมีงานเพิ่มขึ้นอย่างชัดเจนที่นี่เพื่อให้เสร็จสมบูรณ์ developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone

ฉันกำลังดู แต่มีปัญหาที่ฉันไม่สามารถแก้ปัญหาได้ในการทำให้เกิดซ้ำฉันต้องเรียกเมธอดสำหรับซูเปอร์คลาส (เช่นในบรรทัดสุดท้ายของโค้ดก่อนหน้า) เนื่องจาก NSObject เป็นคลาสรูทที่จะไม่ทำงานในหมวดหมู่ . จึงไม่สามารถเรียกซ้ำได้ ... :( ไม่แน่ใจว่าหมวดหมู่ใน NSObject เป็นวิธีที่จะไปอีกต่อไปหรือไม่ ...
PakitoV

3

คำว่า "แอตทริบิวต์" ค่อนข้างคลุมเครือ คุณหมายถึงตัวแปรอินสแตนซ์คุณสมบัติวิธีการที่ดูเหมือน accessors หรือไม่?

คำตอบของทั้งสามคือ "ใช่ แต่มันไม่ง่ายเลย" Runtime API Objective-Cรวมถึงฟังก์ชั่นที่จะได้รับรายชื่อ Ivar รายการวิธีการหรือรายชื่อสถานที่ให้บริการสำหรับการเรียน (เช่นclass_copyPropertyList()) แล้วฟังก์ชั่นที่สอดคล้องกันสำหรับแต่ละประเภทจะได้รับชื่อของรายการในรายการ (เช่นproperty_getName())

สรุปแล้วอาจเป็นงานที่ต้องทำอย่างถูกต้องหรืออย่างน้อยก็มากกว่าที่คนส่วนใหญ่ต้องการทำเพื่อสิ่งที่มักจะเป็นคุณลักษณะที่ไม่สำคัญจริงๆ

หรือคุณสามารถเขียนสคริปต์ Ruby / Python ที่เพิ่งอ่านไฟล์ส่วนหัวและค้นหาสิ่งที่คุณพิจารณา "แอตทริบิวต์" สำหรับคลาส


สวัสดีขอบคุณสำหรับการตอบกลับของคุณ สิ่งที่ฉันอ้างถึงกับ "คุณลักษณะ" นั้นเป็นคุณสมบัติของคลาส ฉันจัดการเพื่อให้บรรลุสิ่งที่ต้องการได้แล้วโดยใช้ Obj-C Runtime Library การใช้สคริปต์เพื่อแยกวิเคราะห์ไฟล์ส่วนหัวจะไม่ได้ผลกับสิ่งที่ฉันต้องการบนรันไทม์
boliva

3

ฉันได้รับคำตอบจาก @ orange80 ในการทำงานกับARC ENABLED … ... สำหรับสิ่งที่ฉันต้องการ - อย่างน้อย ... แต่ก็ไม่ได้โดยไม่ต้องลองผิดลองถูกสักนิด หวังว่าข้อมูลเพิ่มเติมนี้อาจช่วยให้ใครบางคนคลายความเศร้าโศกได้

บันทึกชั้นเรียนเหล่านั้นที่เขาอธิบายไว้ในคำตอบ = เป็นชั้นเรียนและใส่AppDelegate.h(หรืออะไรก็ตาม) ของ#import PropertyUtil.hคุณ แล้วในของคุณ ...

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

วิธีการ (หรืออะไรก็ได้)

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

เคล็ดลับคือการโยนตัวแปรอินสแตนซ์ของคลาสของคุณ ( ในกรณีนี้คลาสของฉันคือGistและอินสแตนซ์ของฉันGistคือgist ) ที่คุณต้องการสอบถาม ... ไปยัง NSObject ... (id)ฯลฯ จะไม่ตัดมัน .. สำหรับหลาย ๆ อย่างแปลก เหตุผลลึกลับ สิ่งนี้จะทำให้คุณได้ผลลัพธ์เช่นนั้น ...

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

สำหรับ OCD ที่ไม่สะทกสะท้าน / OCD ทั้งหมดของ Apple ที่คุยโวเกี่ยวกับการวิปัสสนา "amazeballs" ของ ObjC ... พวกเขาแน่ใจว่าจะไม่ทำให้การ "มอง" "ที่เป็นตัวของตัวเอง" "ที่เป็นตัวของตัวเอง" "ที่เป็นตัวของตัวเอง" ง่าย ๆ นี้เป็นเรื่องง่าย

หากคุณต้องการที่จะคลั่งไคล้จริงๆ .. ลองดู .. class-dumpซึ่งเป็นวิธีที่บ้าคลั่งในการมองเข้าไปในส่วนหัวของชั้นเรียนของปฏิบัติการใด ๆ ฯลฯ ... มันให้ VERBOSE ดูในชั้นเรียนของคุณ ... ว่าฉัน, โดยส่วนตัวพบว่ามีประโยชน์อย่างแท้จริง - ในหลาย ๆ สถานการณ์ นั่นเป็นเหตุผลที่ฉันเริ่มหาวิธีแก้ปัญหาสำหรับคำถามของ OP นี่คือพารามิเตอร์การใช้งานบางส่วน .. สนุก!

    -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

คุณมีเวทมนตร์สามคาถา

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.

โค้ดต่อไปนี้สามารถช่วยคุณได้

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

ฉันใช้ฟังก์ชั่นโบลิวาที่ให้มา แต่เห็นได้ชัดว่ามันหยุดทำงานกับ iOS 7 ดังนั้นตอนนี้แทนที่จะเป็น Const const char * getPropertyType (คุณสมบัติ objc_property_t) สามารถใช้สิ่งต่อไปนี้:

- (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:@""];
}

คุณคือฮีโร่ของฉัน. ฉันยังคงต้องแก้ไขบางสิ่งด้วยตนเอง (ด้วยเหตุผลบางอย่าง BOOL จึงขึ้นมาเป็น 'Tc') แต่สิ่งนี้ทำให้ฉันสามารถทำงานได้อีกครั้ง
Harpastum

Primitives มีประเภทของตัวเอง "@" หมายถึงวัตถุและหลังจากนั้นชื่อคลาสจะปรากฏขึ้นระหว่างเครื่องหมายคำพูด ข้อยกเว้นเพียงอย่างเดียวคือ id ที่เข้ารหัสเป็น "T @"
Mihai Timar

2

สำหรับผู้ชมอย่างรวดเร็วคุณสามารถใช้ฟังก์ชันนี้ได้โดยใช้Encodableฟังก์ชันนี้ ฉันจะอธิบายว่า:

  1. สอดคล้องกับวัตถุของคุณกับEncodableโปรโตคอล

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. สร้างส่วนขยายสำหรับEncodableเพื่อให้มีtoDictionaryฟังก์ชันการทำงาน

     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. เรียกtoDictionaryใช้อินสแตนซ์วัตถุของคุณและเข้าถึงkeysคุณสมบัติ

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. โวลา! เข้าถึงคุณสมบัติของคุณดังนี้:

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

1

คำตอบเหล่านี้มีประโยชน์ แต่ฉันต้องการมากกว่านั้น สิ่งที่ฉันต้องการทำคือตรวจสอบว่าประเภทคลาสของคุณสมบัติเท่ากับของวัตถุที่มีอยู่หรือไม่ รหัสทั้งหมดข้างต้นไม่สามารถทำได้เนื่องจาก: ในการรับชื่อคลาสของวัตถุ object_getClassName () จะส่งคืนข้อความดังนี้:

__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:])

แต่ถ้าเรียกใช้ getPropertyType (... ) จากโค้ดตัวอย่างด้านบนให้ใช้ 4 objc_property_t โครงสร้างของคุณสมบัติของคลาสที่กำหนดไว้เช่นนี้:

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

มันส่งคืนสตริงตามลำดับดังต่อไปนี้:

NSArray
NSArray
NSNumber
NSValue

ดังนั้นจึงไม่สามารถระบุได้ว่า NSObject สามารถเป็นค่าของคุณสมบัติหนึ่งของคลาสได้หรือไม่ จะทำอย่างไรนั้น?

นี่คือโค้ดตัวอย่างแบบเต็มของฉัน (ฟังก์ชัน getPropertyType (... ) เหมือนกับด้านบน):

#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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.