Search
Rich's Mad Rants
Powered by Squarespace

Entries in NSUbiquitousKeyValueStore (1)

Wednesday
Jan252012

Automatically Syncing User Defaults

OK, I did a quick proof of concept based partially on my discussion of iCloud's key/value storage with Saul on NSBrief, and partially on the previous NSBrief episode about metaprogramming. I thought it would be interesting to create a class that used metaprogramming to automatically set up NSUserDefaults syncing via iCloud.

Usage

Here's how it works. I created an abstract class called RWUserDefaultsManager. To use this class, simply create a concrete subclass and add dynamic properties for all the default values you wish to use.

In the sample code, I created a DefaultManager class with the following header:

#import "RWUserDefaultManager.h"

 

@interface DefaultManager : RWUserDefaultManager

 

@property (assign)NSString* name;

@property (assign)NSDate* date;

 

@end

These properties are then declared as @dynamic in the implementation file:

@implementation DefaultManager

 

@dynamic name;

@dynamic date;

 

@end

I can then access the shared DefaultManager anywhere in my code, and use these properties to get or set my user defaults.

 

- (IBAction)changed:(id)sender {

 

DefaultManager* defaultManager =

[DefaultManager sharedManager];

 

NSDate* date = [NSDate date];

 

self.dateLabel.text =

[NSDateFormatter localizedStringFromDate:date

dateStyle:NSDateFormatterMediumStyle

timeStyle:NSDateFormatterShortStyle];

 

defaultManager.date = date;

defaultManager.name = self.nameTextField.text;

}

The default manager will also post a RWUserDefaultsChangedNotification whenever the defaults change (e.g. from the Settings app or from an iCloud update). I simply listen to that notification and update my UI.

That's it. It's very simple to use. It forces me to explicitly declare my user defaults, and it transparently manages all the iCloud syncing.

Metaprogramming

The metaprogramming kicks in when the class is first loaded. It scans through all the properties looking for dynamic properties. Then it creates three methods for those properties, the getter, the setter and an update method (used to update the user defaults whenever the iCloud data changes).

I'm going to gloss over the metaprogramming, except to say that it uses class_copyPropertyList() to access the list of properties. property_getAttributes() and property_getNames() to get the information we need about the properties. imp_implementationWithBlock() to convert Objective-c blocks to IMP objects, and class_addMethod() to add the methods to our class. All of these methods are from the Objective-C Runtime. Check out the sample code to see how they are used (as always, if you'd like a longer post specifically on metaprogramming, say so in the comments--if I get enough interest, I'll see what I can do).

Currently it's limited to plist objects (NSData, NSString, NSNumber, NSDate, NSArray and NSDictionary). It doesn't do any type checking--so if you try to use a different object, it will cause a runtime error. It also supports most of the primitive C types (BOOL, char, int, long, long long, float and double).

I'd like to expand it to do some auto-boxing--for example, of commonly used structures or automatically converting objects that adopt NSCoding into NSData objects so they can be stored, but that's work for later on. Right now, you can implement your own auto-boxing by simply declaring a public property of the type you wish to store:

@property (assign)CGPoint center;

Then, in the implementation file, declare private properties to hold the actual data:

@interface DefaultManager ()

 

@property (assign)CGFloat centerX;

@property (assign)CGFloat centerY;

 

@end

Declare these properties as dynamic, as usual

@dynamic centerX;

@dynamic centerY;

Then implement custom getters and setters to read and load the values

-(void)setCenter:(CGPoint)center {

 

self.centerX = center.x;

self.centerY = center.y;

}

 

-(CGPoint)center {

 

return CGPointMake(self.centerX,

self.centerY);

}

Other Notes:

  • The library should work for either iOS or OS X Lion. However, I haven't tested it yet on Lion.
  • The sample code has a bit more repetition than I'm really comfortable with. I'd like to refactor it to get rid of the repetition and longer methods.
  • I've lightly tested the code but haven't done any robust testing yet. Please take a look, check it out and give me any feedback/suggestions/criticisms/whatever.
  • Remember, iCloud's key/value storage doesn't support conflict resolutions--the last change always wins. It also has rather strict memory limits. This code doesn't check to make sure you're staying under those limits.

Sample Code

Check out the sample code here.