Skip to content

Instantly share code, notes, and snippets.

@mrtj
Last active December 14, 2015 04:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrtj/5030230 to your computer and use it in GitHub Desktop.
Save mrtj/5030230 to your computer and use it in GitHub Desktop.
A wrapper class around NSUserDefaults that adds compile time type and spell check to keys stored in user defaults. Usage: inherit from this class and define your properties in your header (currently only NSInteger and BOOL types are supported). In the implementation mark all properties as @dynamic much like in managed object derivates. All of yo…
#import <Foundation/Foundation.h>
@interface TJSettingsManager : NSObject
{
NSUserDefaults* _userDefaults;
}
+(id)sharedInstance;
-(void)save;
@end
#import "TJSettingsManager.h"
#import <objc/runtime.h>
#pragma mark - Utility functions for resolving setter & getter names
char* setterNameForProperty(objc_property_t property, char** dest)
{
const char* propertyName = property_getName(property);
// TODO: check last property attribute for overridden setter name
unsigned int propertyNameLength = strlen(propertyName);
const char* prefix = "set";
const char* suffix = ":";
unsigned int prefixLength = strlen(prefix);
unsigned int suffixLength = strlen(suffix);
unsigned int destLength = propertyNameLength + prefixLength + suffixLength + 1;
*dest = malloc(destLength * sizeof(char));
char *ptr = *dest;
strcpy(ptr, prefix); ptr += prefixLength;
*ptr = propertyName[0] + 'A' - 'a'; ptr++; // first letter uppercase
strcpy(ptr, propertyName + 1); ptr += propertyNameLength - 1;
strcpy(ptr, suffix); ptr += suffixLength;
*ptr = '\0';
return *dest;
}
char* getterNameForProperty(objc_property_t property, char** dest)
{
const char* propertyName = property_getName(property);
// TODO: check last property attribute for overridden getter name
unsigned int propertyNameLength = strlen(propertyName);
*dest = malloc((propertyNameLength + 1) * sizeof(char));
char *ptr = *dest;
strcpy(ptr, propertyName); ptr += propertyNameLength;
*ptr = '\0';
return *dest;
}
char* propertyNameFromSetter(const char* setterName, char** dest)
{
const char* prefix = "set";
const char* suffix = ":";
unsigned int prefixLength = strlen(prefix);
unsigned int suffixLength = strlen(suffix);
unsigned int srcLength = strlen(setterName);
unsigned int destLength = srcLength - prefixLength - suffixLength;
if (strncmp(setterName, prefix, prefixLength)) {
// setterName should start with "set"
*dest = NULL;
return *dest;
}
if (strncmp(setterName + srcLength - suffixLength, suffix, suffixLength)) {
// setterName should finish with ":"
*dest = NULL;
return *dest;
}
const char *srcPtr = setterName + prefixLength;
if (srcPtr[0] < 'A' || srcPtr[0] > 'Z') {
// after "set" an uppercase letter is expected
*dest = NULL;
return *dest;
}
*dest = malloc((destLength + 1) * sizeof(char));
char *destPtr = *dest;
destPtr[0] = srcPtr[0] - 'A' + 'a'; srcPtr++; destPtr++; // first letter lowercase
strncpy(destPtr, srcPtr, destLength - 1); destPtr += destLength - 1; srcPtr += destLength - 1;
*destPtr = '\0';
return *dest;
}
char* propertyNameFromGetter(const char* getterName, char** dest)
{
unsigned int srcLength = strlen(getterName);
unsigned int destLength = srcLength + 1;
*dest = malloc(destLength * sizeof(char));
const char *srcPtr = getterName;
char *destPtr = *dest;
strncpy(destPtr, srcPtr, srcLength); destPtr += srcLength; srcPtr += srcLength;
*destPtr = '\0';
return *dest;
}
#pragma mark - Generic setters and getters
char dynamicBoolGetter(id self, SEL _cmd)
{
char* propertyName = NULL;
propertyNameFromGetter(sel_getName(_cmd), &propertyName);
if (propertyName) {
NSString* propertyNameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
return [[NSUserDefaults standardUserDefaults] boolForKey:propertyNameString];
} else {
return 0; // could assert here
}
}
int dynamicIntGetter(id self, SEL _cmd)
{
char* propertyName = NULL;
propertyNameFromGetter(sel_getName(_cmd), &propertyName);
if (propertyName) {
NSString* propertyNameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
return [[NSUserDefaults standardUserDefaults] integerForKey:propertyNameString];
} else {
return 0; // could assert here
}
}
id dynamicStringGetter(id self, SEL _cmd)
{
char* propertyName = NULL;
propertyNameFromGetter(sel_getName(_cmd), &propertyName);
if (propertyName) {
NSString* propertyNameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
return [[NSUserDefaults standardUserDefaults] stringForKey:propertyNameString];
} else {
return nil; // could assert here
}
}
void dynamicBoolSetter(id self, SEL _cmd, char value)
{
char* propertyName = NULL;
propertyNameFromSetter(sel_getName(_cmd), &propertyName);
if (propertyName) {
NSString* propertyNameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
[[NSUserDefaults standardUserDefaults] setBool:value forKey:propertyNameString];
free(propertyName);
} // else could assert here
}
void dynamicIntSetter(id self, SEL _cmd, int value)
{
char* propertyName = NULL;
propertyNameFromSetter(sel_getName(_cmd), &propertyName);
if (propertyName) {
NSString* propertyNameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
[[NSUserDefaults standardUserDefaults] setInteger:value forKey:propertyNameString];
free(propertyName);
} // else could assert here
}
void dynamicStringSetter(id self, SEL _cmd, id string)
{
char* propertyName = NULL;
propertyNameFromSetter(sel_getName(_cmd), &propertyName);
if (propertyName) {
NSString* propertyNameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
[[NSUserDefaults standardUserDefaults] setObject:string forKey:propertyNameString];
free(propertyName);
} // else could assert here
}
@implementation TJSettingsManager
+(id)sharedInstance
{
static id _sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
_userDefaults = [NSUserDefaults standardUserDefaults];
}
return self;
}
-(void)save
{
[_userDefaults synchronize];
}
+(BOOL)resolveInstanceMethod:(SEL)sel
{
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++)
{
objc_property_t property = properties[i];
const char* propertyName = property_getName(property);
const char* propertyAttribs = property_getAttributes(property);
char* getterName = NULL;
char* setterName = NULL;
getterNameForProperty(property, &getterName);
setterNameForProperty(property, &setterName);
// TODO: do this only for dynamic properties
SEL getterSelector = sel_registerName(getterName);
SEL setterSelector = sel_registerName(setterName);
// fprintf(stdout, "%s %s %s\n", propertyName, propertyAttribs, setterName);
free(setterName);
free(getterName);
if (propertyAttribs[0] == 'T')
{
const char propertyType = propertyAttribs[1];
switch (propertyType) {
case 'c':
class_addMethod([self class], setterSelector, (IMP) dynamicBoolSetter, "v@:c");
class_addMethod([self class], getterSelector, (IMP) dynamicBoolGetter, "c@:");
break;
case 'i':
class_addMethod([self class], setterSelector, (IMP) dynamicIntSetter, "i@:");
class_addMethod([self class], getterSelector, (IMP) dynamicIntGetter, "v@:i");
break;
case '@':
{
const char* typePtr = propertyAttribs + 2;
size_t typeLength = strcspn(typePtr, ",");
if (strncmp(typePtr, "\"NSString\"", typeLength) == 0) {
class_addMethod([self class], setterSelector, (IMP) dynamicStringSetter, "v@:@");
class_addMethod([self class], getterSelector, (IMP) dynamicStringGetter, "@@:");
}
break;
}
default:
break;
}
}
}
free(properties);
properties = nil;
});
return [super resolveInstanceMethod:sel];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment