Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

iPhone Preferences - An Approach

DZone's Guide to

iPhone Preferences - An Approach

· Integration Zone
Free Resource

Share, secure, distribute, control, and monetize your APIs with the platform built with performance, time-to-value, and growth in mind. Free 90 day trial 3Scale by Red Hat

There are a lot of articles on the web and a section in every iPhone SDK book about storing application preferences.  This is not another article on how to do that.  This is an article on my approach to utilizing preferences throughout an application.  But for those unfamiliar, here's a brief rundown of the problem domain and how preferences generally work for the iPhone.

Most books and article will tell you there are basically two ways to store preferences; using an NSMutableDictionary persisted to a plist file or a Settings.bundle.  When using a Settings.bundle you have to know up front every possible setting for any given key.  For example, if setting a preference for a default sex, at least on earth, we know is is going to always be Male or Female.  A Settings.bundle does the job of creating all the preference UI elements for us and access to these screens are done outside of your application in the iPhones core Settings menu system.

Using the plist file approach you have a bit more freedom because you won't always know every possible setting.  For example, say you needed to store a specific Username for a login form.  You can't possibly know every username.  The down side is you must construct your preferences UI for your users manually.

The books and articles on the web do a really good job covering both approaches so I need not go into any more detail than that.  However, they don't do a good job of explaining the best way to use your preferences throughout your application.  There's a bit of code involved with the plist option (which is what I'm covering in this article) and you don't want that code littered all over your code base.  You also want a simple way to access each preference and write out new values for those preferences when need be.

For my solution I turned to an old pattern friend of mine called the Singleton.  A singleton is basically an instance of a class that only exists once.  It used to be quite popular but then patterns like IoC became a better solution most often however, for this particular problem it works very well.  Basically what we want to do is create a single instance of a manager class that allows us to access all of our preferences.  Let's start with the header file:

#import <Foundation/Foundation.h>

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

@property (nonatomic, retain) NSMutableDictionary *prefs;

@end

We start with an NSMutableDictionary that is going to hold all of our preferences in key/value pairs.  We also have an NSString that holds a reference to where our plist file will be located.  Each iPhone application has it's own directory layout and part of that structure is a Documents folder.  This is where we want to store the plist file.  Now we can get into the implementation which will show how we get access to this location via the 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

 We have a static sharedInstance which becomes our single instance of this class in memory.  In the init method we call loadPrefs which located our Documents folder and stores that in prefsFilePath.  loadPrefs first checks to see if we have that path, if not, it fetches it.  Then it checks to see if a plist file exists at that location.  If it does, we're good.  We then load the NSMutableDictionary with all the values.  Otherwise, we create the dictionary and load it up with default preferences, and save that out to disk.  This code doesn't exist yet because I need to go over the next bit first.

So far so good.  We have our header file and we have our singleton implementation but now we need to actually store and retrieve some preferences.  First, let's define a few keys and default values in our header file.  Above the interface declaration we'll add the following:

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

 We're going to save a value indicating if this is the first time we've ran this application as well as a default value for sex.  In our case, Male.  Next, we want to store these values if a plist file doesn't exist yet.  In the final else block inside loadPrefs, we'll add the following code:

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];

We create an instance of our dictionary and then we set key/value pairs using our #define's.  Then we save the dictionary out to our prefsFilePath which saves all the values into a plist file.  Now we need a simple way to access these properties to both read and write them but we don't want to have to remember all the #define values for all the keys.  For this I simply chose to use mutator methods (getters and setters) for each preference.  Add the following to the header file:

-(void)savePrefs;

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

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

I'll explain the savePrefs in a bit but the other method declarations should be self explanitory.  Now we need to implement these methods:

-(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];
}

And that's it.  Our savePrefs method is there so we can access it from our sharedInstance.  And here's how we use it.  Include the header file in your new class.  Then you just need to do something like this:

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]];

The only thing left to remember is that when you exit your app, release the prefsManager instance so you don't have memory leak issues.  I'd be interested to hear how other iphone developers have solved this as I'm always looking for better ways to architecture my applications.

Discover how you can achielve enterpriese agility with microservices and API management

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}