Простой способ сделать класс с поддержкой NSCoder - PullRequest
6 голосов
/ 20 января 2012

У меня есть тонны объектов, которые я хочу сохранить для автономного использования. В настоящее время я использую классы, совместимые с NSCoder, для объектов и кодированных данных, которые будут доступны в автономном режиме.

Итак, в .h я представляю объекты:

@interface MyClass : NSObject<NSCoding>{
NSNumber* myObject;}
@property(nonatomic,retain) NSNumber* myObject;

А в .м я делаю начальные единицы:

- (id) initWithCoder: (NSCoder *)coder {
   if (self = [super init]) {
        [self setMyObject: [coder decodeObjectForKey:@"myObject"]];
   }
}

- (void) encodeWithCoder: (NSCoder *)coder { 
    [coder encodeObject: myObject forKey:@"myObject"];
}

Таким образом, класс - просто фиктивное хранилище с геттером и сеттером. Есть ли лучший способ сделать декодирование / кодирование. Можно ли как-нибудь использовать @dynamic или Key-value для кодирования и декодирования? По сути, я хочу, чтобы все переменные в классе сохранялись в файл и обратно в объект при запуске программы. Этот подход работает, но создание всех классов требует времени и усилий.

Ответы [ 2 ]

43 голосов
/ 20 января 2012

Да, вы можете сделать это автоматически. Сначала импортируйте их в свой класс:

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

Теперь добавьте этот метод, который будет использовать низкоуровневые методы для получения имен свойств:

- (NSArray *)propertyKeys
{
    NSMutableArray *array = [NSMutableArray array];
    Class class = [self class];
    while (class != [NSObject class])
    {
        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
        for (int i = 0; i < propertyCount; i++)
        {
            //get property
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            NSString *key = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];

            //check if read-only
            BOOL readonly = NO;
            const char *attributes = property_getAttributes(property);
            NSString *encoding = [NSString stringWithCString:attributes encoding:NSUTF8StringEncoding];
            if ([[encoding componentsSeparatedByString:@","] containsObject:@"R"])
            {
                readonly = YES;

                //see if there is a backing ivar with a KVC-compliant name
                NSRange iVarRange = [encoding rangeOfString:@",V"];
                if (iVarRange.location != NSNotFound)
                {
                    NSString *iVarName = [encoding substringFromIndex:iVarRange.location + 2];
                    if ([iVarName isEqualToString:key] ||
                        [iVarName isEqualToString:[@"_" stringByAppendingString:key]])
                    {
                        //setValue:forKey: will still work
                        readonly = NO;
                    }
                }
            }

            if (!readonly)
            {
                //exclude read-only properties
                [array addObject:key];
            }
        }
        free(properties);
        class = [class superclass];
    }
    return array;
}

Тогда вот ваши методы NSCoder:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if ((self = [self init]))
    {
        for (NSString *key in [self propertyKeys])
        {
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    for (NSString *key in [self propertyKeys])
    {
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}

Вы должны быть немного осторожны с этим. Существуют следующие предостережения:

  1. Это будет работать для свойств, представляющих собой числа, числа, объекты и т. Д., Но пользовательские структуры не будут работать. Кроме того, если какие-либо свойства в вашем классе являются объектами, которые сами не поддерживают NSCoding, это не сработает.

  2. Это будет работать только с синтезированными свойствами, но не с иварами.

Вы можете добавить обработку ошибок, проверив тип значения в encodeWithCoder перед его кодированием или переопределив метод setValueForUndefinedKey для более изящной обработки проблемы.

UPDATE:

Я обернул эти методы в библиотеку: https://github.com/nicklockwood/AutoCoding - библиотека реализует эти методы как категорию в NSObject, так что любой класс может быть сохранен или загружен, а также добавляет поддержку кодирования унаследованных свойств, который мой оригинальный ответ не обрабатывает.

ОБНОВЛЕНИЕ 2:

Я обновил ответ, чтобы правильно обрабатывать унаследованные и доступные только для чтения свойства.

0 голосов
/ 12 июля 2016

Производительность библиотеки https://github.com/nicklockwood/AutoCoding не очень хорошая, так как она каждый раз делает отражение, используя class_copyPropertyList, и кажется, что она не поддерживает некоторые структуры.

Проверьте https://github.com/flexme/DYCoding, это должно быть так же быстро, как предварительно скомпилированный код, и он поддерживает различные типы свойств.

...