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

в 4:50, , рубрики: in-App Purchase, Блог компании Инетра, Программирование, разработка под iOS, метки:

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

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

Когда настроек становится действительно много, оперировать ими становится неудобно.
В приложении 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:
Сериализуем настройки при помощи протокола NSCoding

Глеб Пинигин, iOS-разработчик

Автор: Inetra

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js