Завдання-C Самоаналіз / рефлексія


79

Чи існує вбудований метод, функція, API, загальновизнаний спосіб тощо для скидання вмісту екземпляра об’єкта в Objective-C, зокрема в середовищі Apple Cocoa / Cocoa-Touch?

Я хочу мати можливість зробити щось подібне

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()) та API API Reflection.


Цікаво, того самого питання є кілька днів. Дякую за це запитання.
Яннік Лоріот,

7
Так, чудове запитання. Однією з найбільших переваг ObjC перед іншими подібними мовами є її дивовижна динамічна система виконання, яка дозволяє робити такі чудові речі. На жаль, люди рідко використовують його у повному обсязі, тому похвала за те, що ви можете навчити спільноту SO вашим запитанням.
rpj

Я створив полегшену бібліотеку для роботи з відображенням OSReflectionKit . За допомогою цієї бібліотеки ви можете просто викликати [the_thing fullDescription].
Alexandre OS

ВЕЛИКЕ питання !! - Чи маєте ви уявлення про те, як встановити фактичну властивість, як тільки знайдете її за допомогою Reflection? У мене є запитання тут: stackoverflow.com/q/25538890/1735836
Патрісія

Відповіді:


112

ОНОВЛЕННЯ: Кожен, хто хоче робити подібні речі, може перевірити обгортку ObjC Майка Еша про час виконання Objective-C .

Це більш-менш, як ви це зробите:

#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, propertiesі methodsмасивів, або ж ви протікання їх.
Dave DeLong

Вупс, забув це. Покладіть їх зараз. Дякую.
Felixyz

4
Чи не можемо ми побачити значення іварів?
Самхан Салахуддін,

2
... І їх типи. Я помітив, що ви сказали, що це можна зробити, але ви, безумовно, набагато краще розумієте це, ніж я. Чи змогли б ви в якийсь момент оновити це за допомогою коду, щоб отримати значення та типи для ivars та властивостей?
Генерал Майк

З ARC, чи все-таки потрібно звільняти ivars, властивості та масиви методів тощо?
Чак Круцінджер,

12

Якщо не descriptionбрати до уваги метод (наприклад, .toString () у Java), я не чув про вбудований, але створити його було б не надто складно. Посилання на час виконання Objective-C містить купу функцій, які ви можете використовувати для отримання інформації про змінні екземпляра об'єкта, методи, властивості тощо.


+1 за інформацію, саме те, що я шукав. Тим не менш, я сподівався на заздалегідь розроблене рішення, оскільки я досить знайомий з мовою, що все, що я швидко зломлюю, може пропустити деякі основні тонкощі мови.
Алан Сторм,

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
Патрісія

@Lucy, будь-який рядок, який ти повертаєш за допомогою цієї техніки, може бути використаний у [myObject setValue: myValue forKey: propertyName]. Методика, яку я описав вище (за допомогою списку властивостей), є відображенням ... ви також можете використовувати масив objc_property_t для безпосереднього виклику методів властивостей, але setValue: forKey: простіший у використанні і працює так само добре.
Kendall Helmstetter Gelner

4

Я зробив кілька налаштувань коду Кендалла для друку значень властивостей, що мені дуже пригодилось. Я визначив його як метод екземпляра замість методу класу, оскільки так його називає рекурсія суперкласу. Я також додав обробку винятків для властивостей, що не відповідають 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
Патрісія

3

Чесно кажучи, правильним інструментом для цієї роботи є налагоджувач Xcode. У ньому є вся ця інформація, доступна візуально. Не поспішайте навчитися користуватися ним, це дійсно потужний інструмент.

Більше інформації:

Використання налагоджувача

Застарілий посібник з налагодження Xcode - архівує Apple

Про налагодження за допомогою Xcode - архівує Apple

Про LLDB та налагодження - архівує Apple

Налагодження за допомогою GDB - архівує Apple

Посібник із налагодження SpriteKit - архівує Apple

Теми програмування налагодження для Core Foundation - архівує Apple


4
+1 для хорошої інформації, але іноді швидше скидати стан об’єкта в дві точки та різнити вихід, ніж це починається у стані програми в один момент часу.
Алан Сторм

1

Я зробив з цього кокоапод, https://github.com/neoneye/autodescribe

Я змінив код Крістофера Піклая і зробив його категорією на 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

Я раніше плутався із самоаналізом та удосконаленням, тому отримайте трохи інформації нижче.

Самоаналіз - це можливість об’єкта перевірити, який це тип, протокол, який він відповідає, або селектор, на який він може відповісти. API objc, такий як isKindOfClass/ isMemberOfClass/ conformsToProtocol/ respondsToSelectorтощо.

Можливість удосконалення - це більше, ніж самоаналіз , Це не тільки може отримувати інформацію про об’єкт, але також може працювати з метаданими, властивостями та функціями об’єкта. такі як object_setClassможуть змінити тип об'єкта.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.