Статьи

Настройки iPhone — подход

В Интернете есть множество статей и раздел в каждой книге iPhone SDK о хранении настроек приложения. Это не другая статья о том, как это сделать. Это статья о моем подходе к использованию предпочтений в приложении. Но для тех, кто незнаком, вот краткое изложение проблемной области и того, как предпочтения обычно работают для iPhone.

Большинство книг и статей скажут вам, что есть два основных способа хранения предпочтений; использование NSMutableDictionary сохраняется в файл plist или файл Settings.bundle. При использовании Settings.bundle вы должны заранее знать все возможные настройки для любой данной клавиши. Например, если мы устанавливаем предпочтение для пола по умолчанию, по крайней мере, на земле, мы знаем, что это всегда будет мужчина или женщина. Параметр Settings.bundle создает для нас все предпочтительные элементы пользовательского интерфейса, и доступ к этим экранам осуществляется вне вашего приложения в основной системе меню настроек iPhone.

Используя подход plist-файла, вы получаете немного больше свободы, потому что вы не всегда будете знать все возможные настройки. Например, скажем, вам нужно сохранить определенное имя пользователя для формы входа. Вы не можете знать каждое имя пользователя. Обратной стороной является то, что вы должны создать свой пользовательский интерфейс предпочтений для своих пользователей вручную.

Книги и статьи в Интернете отлично справляются с обоими подходами, поэтому мне не нужно вдаваться в подробности. Однако они не могут объяснить, как лучше всего использовать ваши предпочтения в приложении. Есть немного кода, связанного с опцией plist (о чем я расскажу в этой статье), и вы не хотите, чтобы этот код был засорен по всей вашей кодовой базе. Вы также хотите получить простой способ доступа к каждому предпочтению и, при необходимости, записывать новые значения для этих предпочтений.

Для моего решения я обратился к своему старому знакомому по имени Синглтон . Синглтон — это в основном экземпляр класса, который существует только один раз. Раньше он был довольно популярен, но потом такие шаблоны, как IoC, стали лучшим решением, однако, для этой конкретной проблемы он работает очень хорошо. По сути, мы хотим создать один экземпляр класса менеджера, который позволит нам получить доступ ко всем нашим предпочтениям. Давайте начнем с заголовочного файла:

#import <Foundation/Foundation.h>

@interface PreferencesManager : NSObject {
NSMutableDictionary *prefs;
NSString *prefsFilePath;
}

@property (nonatomic, retain) NSMutableDictionary *prefs;

@end

Мы начнем с NSMutableDictionary, который будет содержать все наши предпочтения в парах ключ / значение. У нас также есть строка NSString, которая содержит ссылку на то, где будет находиться наш plist-файл. Каждое приложение для iPhone имеет свою собственную структуру каталогов, и частью этой структуры является папка Documents. Здесь мы хотим сохранить файл plist. Теперь мы можем перейти к реализации, которая покажет, как мы получаем доступ к этому местоположению через iPhone API.

#import "PreferencesManager.h"

static PreferencesManager *sharedInstance = nil;

@implementation PreferencesManager

@synthesize prefs;

+(PreferencesManager *)sharedInstance {
@synchronized(self) {
if (sharedInstance == nil)
sharedInstance = [[PreferencesManager alloc] init];
}
return sharedInstance;
}

+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [super allocWithZone:zone];
return sharedInstance; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone {
return self;
}

- (id)init {
[self loadPrefs];
return self;
}


-(void)initPrefsFilePath {
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
prefsFilePath = [documentsDirectory stringByAppendingPathComponent:@"prefs.plist"];
[prefsFilePath retain];
}

-(void) loadPrefs {
if (prefsFilePath == nil) {
[self initPrefsFilePath];
}
if ([[NSFileManager defaultManager] fileExistsAtPath:prefsFilePath]) {
prefs = [[NSMutableDictionary alloc]initWithContentsOfFile:prefsFilePath];
}else{
// load default values for preferences
}
}

@end

 У нас есть статический sharedInstance, который становится нашим единственным экземпляром этого класса в памяти. В методе init мы вызываем loadPrefs, который находится в нашей папке Documents и сохраняет ее в prefsFilePath. Сначала loadPrefs проверяет, есть ли у нас этот путь, а если нет, то выбирает его Затем он проверяет, существует ли файл plist в этом месте. Если это так, у нас все хорошо. Затем мы загружаем NSMutableDictionary со всеми значениями. В противном случае мы создаем словарь, загружаем его с настройками по умолчанию и сохраняем его на диске. Этот код еще не существует, потому что мне нужно сначала перейти к следующему биту.

Все идет нормально. У нас есть файл заголовка, и у нас есть одноэлементная реализация, но теперь нам нужно сохранить и получить некоторые настройки. Во-первых, давайте определим несколько ключей и значений по умолчанию в нашем заголовочном файле. Над объявлением интерфейса мы добавим следующее:

#define FIRST_RUN_KEY @"FirstRun"
#define FIRST_RUN_VALUE @"1"
#define DEFAULT_SEX_PREF_KEY @"DefaultSex"
#define DEFAULT_SEX_DEFAULT_VALUE @"M"

 Мы собираемся сохранить значение, указывающее, запускаем ли мы это приложение впервые, а также значение по умолчанию для секса. В нашем случае, Мале. Далее, мы хотим сохранить эти значения, если файл plist еще не существует. В последнем блоке else внутри loadPrefs мы добавим следующий код:

prefs = [[NSMutableDictionary alloc]initWithCapacity:2];
[prefs setObject:FIRST_RUN_VALUE forKey:FIRST_RUN_KEY];
[prefs setObject:DEFAULT_SEX_DEFAULT_VALUE forKey:DEFAULT_SEX_PREF_KEY];
[prefs writeToFile:prefsFilePath atomically:YES];

Мы создаем экземпляр нашего словаря и затем устанавливаем пары ключ / значение, используя наши # define. Затем мы сохраняем словарь в нашем prefsFilePath, который сохраняет все значения в файле plist. Теперь нам нужен простой способ доступа к этим свойствам для их чтения и записи, но мы не хотим запоминать все значения #define для всех ключей. Для этого я просто решил использовать методы мутатора (геттеры и сеттеры) для каждого предпочтения. Добавьте следующее в заголовочный файл:

-(void)savePrefs;

-(NSString *)firstRun;
-(void)setFirstRun:(NSString *)value;

-(NSString *)defaultSex;
-(void)setDefaultSex:(NSString *)sex;

Я объясню savePrefs немного, но другие объявления методов должны быть понятны. Теперь нам нужно реализовать эти методы:

-(void)savePrefs {
[prefs writeToFile:prefsFilePath atomically:YES];
}

-(NSString *)firstRun {
return [prefs objectForKey:FIRST_RUN_KEY];
}

-(void)setFirstRun:(NSString *)value {
[prefs setObject:value forKey:FIRST_RUN_KEY];
}


-(NSString *)defaultSex {
return [prefs objectForKey:DEFAULT_SEX_PREF_KEY];
}

-(void)setDefaultSex:(NSString *)sex {
[prefs setObject:sex forKey:DEFAULT_SEX_PREF_KEY];
}

Вот и все. Наш метод savePrefs существует, поэтому мы можем получить к нему доступ из нашего sharedInstance. И вот как мы это используем. Включите заголовочный файл в ваш новый класс. Тогда вам просто нужно сделать что-то вроде этого:

PreferenceManager *prefsManager = [PreferencesManager sharedInstance];

if ([[prefsManager firstRun] isEqualToString:@"1"]) {
// do some first run stuff then change the value so we don't do it next time
[prefsManager setFirstRun:@"0"];
[prefsManager savePrefs];
}

// somewhere else in some code
[sexTextField setText:[prefsManager defaultSex]];

Осталось запомнить только то, что при выходе из приложения выпускайте экземпляр prefsManager, чтобы у вас не возникало проблем с утечкой памяти. Мне было бы интересно услышать, как другие разработчики iphone решили эту проблему, так как я всегда ищу лучшие способы для архитектуры моих приложений.