Преобразовать (или скопировать) объект в экземпляр подкласса в Objective-C

Я хочу преобразовать экземпляр объекта в экземпляр подкласса этого класса объектов, чтобы я мог использовать дополнительные методы и свойства этого подкласса в Objective-C.

Как я могу сделать это так, чтобы не требовать жесткого кодирования свойств этого класса объектов в методе копирования?

2 ответа

Решение

Невозможно преобразовать объект в экземпляр подкласса в Objective-C. Тем не менее, с помощью приведенного ниже класса вы можете предоставить экземпляр объекта и подкласса, а значения всех свойств скопировать в экземпляр подкласса. Эта реализация работает как с объектными типами Objective-C, так и с примитивами C. Вам не нужно указывать (или даже определять) свойства, которые необходимо скопировать, при условии, что вы знаете, что важные переменные видимы и могут быть установлены (т. Е. Нет свойств, которые отображаются как "только для чтения" или нет). выставлен на всех, чьи значения не могут быть пересчитаны классом). Таким образом, этот метод является относительно надежным для известных классов и не требует обновления для поддержки будущих изменений, которые вы сделаете в своем объектном классе, которые соответствуют этим параметрам. Это совместимо с iOS 8.

Этот класс предоставляет четыре метода класса:

+ (id) copyObject:(id)object toSubclassObject:(id)subclassObject

Копирует все свойства объекта в subclassObject. Если subclassObject не является подклассом объекта, возвращается nil.

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

Возвращает словарь всех видимых свойств объекта, включая свойства всех его суперклассов (кроме NSObject).

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

Возвращает словарь всех видимых свойств класса, включая свойства всех его суперклассов (кроме NSObject).

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

Возвращает словарь всех видимых свойств, относящихся к подклассу. Свойства для его суперклассов не включены.

Заголовок:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

Реализация:

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

@implementation SYNUtilities
+ (id) copyObject:(id)object toSubclassObject:(id)subclassObject
{
    if (![[subclassObject class] isSubclassOfClass:[object class]]) {
        return nil;
    }

    NSDictionary * properties = [self propertiesOfObject:object];
    NSLog(@"Properties of %@:\n%@", [object class], properties);

    for (NSString * property in properties) {
        SEL selector = NSSelectorFromString(property);
        if (selector) {
            id value = [object valueForKey:property];
            [subclassObject setValue:value forKey:property];
        }
    }
    return subclassObject;
}

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

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

   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

Мне нужно было создать подкласс NSTextFieldCellиспользуется в NSTableViewи хотел сохранить неизменными свойства, которые были установлены для ячейки в Интерфейсном Разработчике.

Я решил задачу с помощью NSKeyedArchiver, который предназначен для хранения и восстановления свойств объекта.

поскольку NSTextFieldCell реализует initWithCoder, он поддерживает функции архиватора, и поэтому я мог бы использовать этот код для инициализации моего подкласса из свойств другого:

- (id)initWithCell:(NSCell *)cell {
    // Use NSArchiver to copy the NSCell's properties into our subclass
    NSMutableData *data = [NSMutableData data];
    NSKeyedArchiver *arch = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [cell encodeWithCoder:arch];
    [arch finishEncoding];
    NSKeyedUnarchiver *ua = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    self = [self initWithCoder:ua];
    // Here I'd set up additional properties of my own class
    return self;
}
Другие вопросы по тегам