Search
Rich's Mad Rants
Powered by Squarespace

Entries in Metaprogramming (2)

Wednesday
Oct102012

FMSSwizzler library added to GitHub

Hey all.

At the risk of repeating myself, I'll be speaking at the MacTech Conference next week in L.A. I'm actually doing two different presentations. One is on iCloud Document Syncing, the other on Metaprogramming in iOS. In my head, I'm king of thinking them as the "useful" talk and the "fun" talk.

As part of my prep, I've been putting together some examples. Here is another library of useful methods for Objective-C metaprogramming. Specifically, I have methods to simplify method- and class-swizzling. Also, unlike traditional Objective-C metaprogramming--I'm using the block-based API for dynamically adding methods to classes.

I've tried to address all the obvious gotchas (class clusters, KVO, nested dymanic subclasses, etc.), but--obviously--mucking around with classes at runtime will always be a bit dangerous. There are probably a few edge cases that I haven't though of. Take a look at the included documentation and the unit tests, if you have any questions. Most importantly, if you use the code in your own projects (not just when doing exploration coding or debugging), be sure to use it judiciously and test it ferociously.

You can find the repository at https://github.com/rikiwarren/FMSSwizzler. And, you can get more informaiton about MacTech Conf here.

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.