Online Book Reader

Home Category

iOS Recipes - Matt Drance [74]

By Root 234 0
name.

There is one case where use of [self class] could be misleading: if the method in question is called on an instance of a subclass, it returns the subclass rather than the class where the implementation resides. The use of __FILE__ in every PRPLog should clear that up.

With PRPLog and a simple build setting configured to the appropriate scheme in Xcode, your shipping apps will never have stray output again.

Recipe 37 Design Smarter User Defaults Access

Problem

NSUserDefaults is the preferred method for storing lightweight user settings and application state, but its key-value system opens up your code to redundancy and careless bugs. You want to protect yourself from the dangers of string literals and repetition while maintaining the ease of use that NSUserDefaults provides.

Solution

You’re probably used to declaring keys, which we use to read and write a lightweight object value to the user defaults. For example, here’s how we might store a username in the user defaults:

NSString *username = @"groucho"

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

[defaults setObject:username forKey:@"prp_username"];

[defaults synchronize];

Reading the value requires us to use the same key in a different spot:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSString *username = [defaults stringForKey:"@prp_username"];

We already have a problem here: we’re tossing string literals around. We can’t easily refactor this, and Xcode can’t help us get it right through autocompletion. You might also notice that the @ symbol is misplaced in the second example. We can solve these problems by declaring a constant to represent the key:

NSString *const PRPDefaultsKeyUsername = @"prp_username";

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

...

[defaults setObject:username forKey:PRPDefaultsKeyUsername];

...

NSString *username = [defaults stringForKey:PRPDefaultsKeyUsername];

This approach is much safer. First, it gives us the benefit of compiler enforcement if we spell the key wrong. (Misspelled literals, by contrast, will simply fail in unpredictable ways at runtime.) Second, if we use formal variables, Xcode will autocomplete the names when we type them. Using constants makes it much harder to pass the wrong key by accident.

But wouldn’t it be great if our NSUserDefaults instance behaved more like a concrete class? If each of our preferences or keys was represented by a concrete API, enforceable by the compiler and autocompleted by Xcode? We can accomplish this by using an Objective-C category.

A category on NSUserDefaults lets us get at the defaults the traditional way but now with a more formal contract for each of our preference keys. We’ll define standard getters and setters to represent our keys, but rather than storing the values, the methods just pass them on to the standard key-value user defaults implementation.

SmartUserDefaults/NSUserDefaults+PRPAdditions.m

- (NSString *)prp_username {

return [self stringForKey:PRPDefaultsKeyUsername];

}

- (void)prp_setUsername:(NSString *)username {

[self setObject:username forKey:PRPDefaultsKeyUsername];

}

Note the prp_ prefix on the method names once again. This is important whenever we use categories: it reduces the likelihood of accidentally overriding any methods Apple adds to a class in the future. It’s not very likely that a ‑setUsername method will show up in NSUserDefaults any time soon, but prefixing category methods is very cheap insurance. Make a habit of it.

We can even represent these new methods as properties in order to use them with dot syntax. We need custom getter and setter attributes so we can properly prefix the accessor methods.

SmartUserDefaults/NSUserDefaults+PRPAdditions.h

@interface NSUserDefaults (PRPAdditions)

@property (assign, getter=prp_isAutoLoginEnabled,

setter=prp_setAutoLoginEnabled:) BOOL prp_autoLoginEnabled;

@property (assign, getter=prp_isCachingEnabled,

setter=prp_setCachingEnabled:) BOOL prp_cachingEnabled;

@property (assign, getter=prp_username,

Return Main Page Previous Page Next Page

®Online Book Reader