Last active
December 14, 2015 04:39
-
-
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…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@interface TJSettingsManager : NSObject | |
{ | |
NSUserDefaults* _userDefaults; | |
} | |
+(id)sharedInstance; | |
-(void)save; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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