Инетра
Компания
15,59
рейтинг
9 июля 2013 в 08:50

Разработка → Сериализуем настройки при помощи протокола NSCoding

При разработке практически любого приложения рано или поздно появляется необходимость хранить его настройки, будь то текущая версия или in-app настройки приложения. Что в данном случае делает разработчик? Сохраняет данные настройки через NSUserDefaults и правильно делает.



Когда настроек становится действительно много, оперировать ими становится неудобно.
В приложении Peers.TV мы использовали следующую уловку — архиваторы и NSCoding протокол. Это помогло объединить нам часть настроек в рамках одного домена и немного облегчить работу с ними.


Для справки. Приложение Peers.TV предназначено для просмотра ТВ online и архива телепередач. Сегодня это 30 каналов различной тематики. В нем есть программа на неделю вперед, напоминания о времени передачи, встроенные покупки, архив, — всё это требует своих отдельных настроек, что и привело нас к протоколу NSCoding.

Рассмотрим реализацию протокола NSCoding на примере настроек блокирования приложения от нежелательного доступа третьими лицам.
Реализация работы с настройками выносится в отдельный класс-Singleton, а сами настройки реализуются через свойства данного класса.

Предположим, что у нас есть 5 различных настроек, представленных следующим образом:

@property (nonatomic, assign) BOOL isPasscodeEnabled;
@property (nonatomic, assign) NSUInteger attemptCount;
@property (nonatomic, assign) NSTimeInterval checkInterval;
@property (nonatomic, strong) NSDate *nextCheckDate;
@property (nonatomic, strong) NSString *applicationVersion;


Соответственно для каждой настройки нам потребуется объявить дополнительно по одному ключу. Код, ответственный за инициализацию и реализацию свойств, так же пропущен и будет приведен ниже.

А теперь вернемся к протоколу NSCoding. Данный протокол позволяет задать порядок сериализации и десериализации данных объекта.

Для реализации данного протокола необходимо реализовать 2 метода:

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;


Также нам потребуются классы NSKeyedArchiver и NSKeyedUnarchiver, которые сделают всю работу за нас. Стоит отметить, что данные классы позволяют провести сериализацию и десериализацию данных по ключам.

Далее опишем класс, который будет объединять настройки для блокировки приложения.

Интерфейс:

@interface PassCodeSettings: NSObject<NSCoding>

@property (nonatomic, assign) BOOL isPasscodeEnabled;
@property (nonatomic, assign) NSUInteger attemptCount;
@property (nonatomic, assign) NSTimeInterval checkInterval;
@property (nonatomic, strong) NSDate *nextCheckDate;

@end


Реализация:

@implementation PassCodeSettings

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        _attemptCount = [aDecoder decodeIntegerForKey:@"attemptCount"];
        _isPasscodeEnabled = [aDecoder decodeBoolForKey:@"isPassCodeEnabled"];
        _checkInterval = [aDecoder decodeDoubleForKey:@"checkInterval"];
        _nextCheckDate = [NSDate dateWithTimeIntervalSince1970:[aDecoder decodeDoubleForKey:@"nextCheckDate"]];
    }
   
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeBool:_isPasscodeEnabled forKey:@"isPassCodeEnabled"];
    [aCoder encodeInteger:_attemptCount forKey:@"attemptCount"];
    [aCoder encodeDouble:_checkInterval forKey:@"checkInterval"];
    [aCoder encodeDouble:[_nextCheckDate timeIntervalSince1970] forKey:@"nextCheckDate"];
}

@end


Теперь настройки для блокировки приложения вынесены в отдельный класс, соответствующий протоколу NSCoding. Используя NSKeyedArchiver, NSKeyedUnrachiver, мы можем реализовать сохранение произвольного класса в настройках.

Инициализация и сохранение настроек будут выглядеть следующим образом:

- (id)init {
    self = [super init];
    if (self) {
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        _applicationVersion = [defaults stringForKey:kApplicationVersion];
        
        NSData *passCodeData = [defaults dataForKey:kPasscodeSettings];
        if (passCodeData) {
            _passCodeSettings = [NSKeyedUnarchiver unarchiveObjectWithData:passCodeData];
        } else {
            _passCodeSettings = [PassCodeSettings new];
        }
    }
    
    return self;
}


- (void)savePasscodeSettings {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:_passCodeSettings] forKey:kPasscodeSettings];
    [defaults synchronize];
}


Стоит упомянуть о нескольких приемах, которые могут сделать вашу жизнь легче:
1. Чтобы не сохранять настройки вручную методом savePasscodeSettings, можно реализовать наблюдение за свойствами по принципам KVO(Key Value Observing) или использовать уведомления жизненного цикла приложения.
2. Можно также рассылать уведомления об изменении настроек через NSNotificationCenter.

Ссылка на интересную статью о NSCoding-протоколе от небезызвестного Майка Аша(Mike Ash).

Чего мы добились:
— Вынесли часть настроек в один домен, теперь мы можем сгруппировать методы по работе с данными домена в рамках этого домена;
— Немного облегчили работу с настройками.

Удивительно, но мало кто использует в своих проектах NSCoding для сериализиации данных, хотя, на мой взгляд, незаслуженно. Конечно, судить об этом я могу только проектам с открытым кодом.
Данный подход — не серебряная пуля, это вариант среди множества других. Он хорошо вписался в структуру нашего приложения и облегчил нам разработку.

На этом все, исходный код данного примера доступен здесь.

Наше приложение Peers.TV, в котором был использован NSCoding, можно посмотреть на AppStore:


Глеб Пинигин, iOS-разработчик
Автор: @Inetra
Инетра
рейтинг 15,59
Компания прекратила активность на сайте

Комментарии (18)

  • 0
    А если параметров много и не хочется каждый перебирать ручками, то кодирование/декодироание можно сделать так:

    - (void)encodeWithCoder:(NSCoder *)encoder
    {
        unsignedint outCount, i;
        objc_property_t *properties = class_copyPropertyList([selfclass], &outCount);
        for (i = 0; i < outCount; i++)
        {
            objc_property_t property = properties[i];
            NSString * name = [NSStringstringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            id value = [selfvalueForKey:name];
            [encoder encodeObject:value forKey:name];
        }
    }
    
    - (id)initWithCoder:(NSCoder *)decoder
    {
        if((self = [superinit]))
        {
            unsignedint outCount, i;
            objc_property_t *properties = class_copyPropertyList([selfclass], &outCount);
            for (i = 0; i < outCount; i++)
            {
                objc_property_t property = properties[i];
                NSString * name = [NSStringstringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
                id value = [decoder decodeObjectForKey:name];
                [selfsetValue:value forKey:name];
            }
        }
        
        return self;
    }
    
    • 0
      В вашем варианте предполагается, что все свойства — объекты.
      В статье описывается так же кодирование примитивных типов.
      • 0
        Этот вариант тоже можно обыграть, кода будет немного больше, но его придется написать только один раз.
    • 0
      Можно лишнего насохранять много да и свойства не property упустить. Чем меньше лезешь в рантайм тем код менее зависим от его изменений. Я бы тогда уже посоветовал создавать у каждого класса статический метод, который хранит набор ключей для архивации:
      + (NSArray*)keysForArchiving
      {
      	return [[super keysForArchiving] arrayByAddingObjectsFromArray:@[@"title", @"status", @"_privateIvar"]];
      }
      
      - (void)encodeWithCoder:(NSCoder *)encoder
      {
      	for (NSString* key in [[self class] keysForArchiving])
      		[encoder encodeObject:[self valueForKey:key] forKey:key];
      }
      
      - (id)initWithCoder:(NSCoder *)decoder
      {
      	self = [super init];
      	
          if(self)
          {
      	    for (NSString* key in [[self class] keysForArchiving])
          		[self setValue:[decoder decodeObjectForKey:key] forKey:key]; 
          }
          
          return self;
      }
      
      • 0
        Нарушив тем самым SRP :)
        • 0
          Не силен в этом вопросе, но все же, как нарушает?
          • 0
            В этом случае выходит что объект (POD по сути) отвечает еще и за то какие данные он должен хранить.

            Но я утрирую, конечно :)
            • 0
              Думаю, что тогда и к протоколу его будет конформить неправильно.
              • 0
                Да, с некоторой натяжкой это тоже нарушение, IMHO.
  • 0
    В своем проекте применял NSCoding для хранения введенных в форму поиска данных, архивации настроек. Очень удобно.
  • +1
    На хабре странная тенденция просвещения темы iOS разработки — про самые тривиальные вещи пишут топики. Неужели тема, которая сводится к одной страничке документации и полностью прозрачна, достойна поста на хабре?
    • 0
      Порой кажется, что iOS-разработка уже обсосана со всех сторон, и постить нечего.
      Да и правда, зачем постить, если stackoverflow решит все проблемы
      • 0
        Серьезно, за такими темами надо лезть на SO? Скоро туда уже за примерами if else бегать будут.
        • 0
          Я не говорил конкретно про эту тему.
          Просто когда в гугле формулируешь вопрос, обычно вываливается SO, а потом только документация.
          • 0
            Ок, сорри, не так понял. Вообще предпочитаю и сотрудникам рекомендую использовать следующий подход. Надо разобраться как работает технология (например архивация) идем в доку и там читаем все об архивации от А до Я, чтоб знания небыли поверхностными, а то часто лишнего сохраняют, а о encodeConditionalObject: никто даже не знает. Это касается любых других фреймворков/технологий.
    • 0
      • 0
        Актуальный материал на октябрь 2011, так-то
      • 0
        В общем согласен, но тогда эта технология была нова и она описана чуть шире чем приконформиться к протоколу и описать два метода.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка