Objective-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()) и OO Reflection API.

Алан Сторм
источник
Интересно, тот же вопрос есть несколько дней. Спасибо за этот вопрос.
Янник Лориот
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массивы, иначе вы их просочите.
Дэйв Делонг,
Woops, забыл об этом. Вставьте их сейчас. Благодарю.
Felixyz
4
Разве мы не можем увидеть стоимость иваров?
Самхан Салахуддин
2
... И их типы. Я заметил, что вы сказали, что это можно сделать, но, похоже, вы определенно понимаете это гораздо лучше, чем я. Сможете ли вы обновить это в какой-то момент с помощью кода, чтобы получить значения и типы для ivars и свойств?
General Mike
С ARC все еще необходимо освобождать ivars, массивы свойств, методов и т. Д.?
Чак Крутингер,
12

Короткие от descriptionметода (как .ToString () в Java), я не слышал , тот , который был построен в, но это не было бы слишком трудно создать. В Objective-C Runtime Reference есть набор функций, которые вы можете использовать для получения информации о переменных экземпляра объекта, методах, свойствах и т. Д.

Дэйв Делонг
источник
+1 за информацию, именно то, что я искал. Тем не менее, я надеялся на готовое решение, поскольку я достаточно новичок в этом языке, и все, что я быстро взломаю, может упустить некоторые важные тонкости языка.
Алан Сторм,
7
Если вы собираетесь проголосовать против ответа, пожалуйста, оставьте комментарий, почему.
Дэйв Делонг,
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: проще в использовании и работает так же хорошо.
Кендалл Хельмштеттер Гельнер
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

Я сделал из этого cocoapod, 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может изменять тип объекта.

Jone
источник