Skip to content

Instantly share code, notes, and snippets.

@jonsterling
Created July 24, 2010 22:04
Show Gist options
  • Select an option

  • Save jonsterling/489017 to your computer and use it in GitHub Desktop.

Select an option

Save jonsterling/489017 to your computer and use it in GitHub Desktop.
JSSettingsController
//
// JSSettingsController.h
//
// Created by Jon on 7/24/10.
// Copyright (c) 2010 Jonathan Sterling. All rights reserved.
//
@interface JSSettingsController : NSObject
@end
extern NSString * const JSSettingsControllerDefaultDidChangeNotification;
//
// JSSettingsController.mm
//
// Created by Jon on 7/24/10.
// Copyright (c) 2010 Jonathan Sterling. All rights reserved.
//
#import <JSSettingsController/JSSettingsController.h>
#import <JSSettingsController/JSSettingsControllerPrivate.h>
#import <objc/runtime.h>
#import <FunctionKit/FunctionKit.h>
NSString * const JSSettingsControllerDefaultDidChangeNotification = @"JSSettingsControllerDefaultDidChangeNotification";
@implementation JSSettingsController
+ (BOOL)resolveInstanceMethod:(SEL)sel {
objc_property_t matchingProperty = [self propertyForSelector:sel];
BOOL success = NO;
if (matchingProperty != NULL) {
NSString *attrs = fk::box(property_getAttributes(matchingProperty));
NSString *encoding = [[[attrs substringFromIndex:1] componentsSeparatedByString:@","] objectAtIndex:0];
std::map<const char *,jssc::accessor_info_t> map;
map[@encode(id)] = jssc::accessor_info<id>();
map[@encode(int)] = jssc::accessor_info<int>();
map[@encode(float)] = jssc::accessor_info<float>();
map[@encode(double)] = jssc::accessor_info<double>();
map[@encode(BOOL)] = jssc::accessor_info<BOOL>();
map[@encode(CGPoint)] = jssc::accessor_info<CGPoint>();
map[@encode(CGRect)] = jssc::accessor_info<CGRect>();
typedef typeof(map) map_t;
for (map_t::const_iterator it = map.begin(); it != map.end(); ++it) {
NSString *prefix = fk::box<const char *>(it->first);
if ([prefix length]) {
if ([encoding hasPrefix:prefix]) {
jssc::accessor_info_t info = it->second;
BOOL isSetter = [self selectorIsSetter:sel];
IMP imp = reinterpret_cast<IMP>(isSetter ? info.setter_ptr : info.getter_ptr);
const char *type = isSetter ? info.setter_enc : info.getter_enc;
success = class_addMethod(self, sel, imp, type);
break;
}
}
}
}
return success ? YES : [super resolveInstanceMethod:sel];
}
+ (BOOL)selectorIsSetter:(SEL)aSelector {
NSString *sel = fk::box(aSelector);
return [sel hasPrefix:@"set"] && [sel hasSuffix:@":"];
}
+ (BOOL)selector:(SEL)aSelector isSetterForProperty:(NSString *)aProperty {
NSString *selector = fk::box(aSelector);
NSString *frontCut = selector.length > 3 ? [selector substringFromIndex:3] : nil;
NSString *backCut = frontCut.length > 1 ? [frontCut substringToIndex:frontCut.length - 1] : nil;
NSComparisonResult r = [backCut compare:aProperty options:NSCaseInsensitiveSearch];
return r == NSOrderedSame;
}
+ (BOOL)selector:(SEL)aSelector isGetterForProperty:(NSString *)aProperty {
return [fk::box(aSelector) compare:aProperty options:NSCaseInsensitiveSearch] == NSOrderedSame;
}
+ (objc_property_t)propertyForSelector:(SEL)aSelector {
NSUInteger outCount;
objc_property_t *properties = class_copyPropertyList(self, &outCount);
objc_property_t theProperty = NULL;
for (int i = 0; i < outCount; i++) {
objc_property_t p = properties[i];
id name = fk::box(property_getName(p));
if ([self selector:aSelector isSetterForProperty:name] || [self selector:aSelector isGetterForProperty:name]) {
theProperty = p;
break;
}
}
free(properties);
return theProperty;
}
+ (NSString *)keyFromSetter:(SEL)aSetter {
NSString *selector = fk::box(aSetter);
NSString *firstPass = [selector substringWithRange:NSMakeRange(3, selector.length - 4)];
return [[firstPass substringToIndex:1].lowercaseString stringByAppendingString:[firstPass substringFromIndex:1]];
}
- (id)valueForUndefinedKey:(NSString *)key {
return [[NSUserDefaults standardUserDefaults] valueForKey:key];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
[[NSUserDefaults standardUserDefaults] setValue:value forKey:key];
}
@end
namespace jssc
{
template <typename T>
struct defaults_implementation
{
static void set(id self, SEL sel, T val)
{
id defaults = [NSUserDefaults standardUserDefaults];
id pair = fk::pair([JSSettingsController keyFromSetter:sel], val);
[defaults setValuesForKeysWithDictionary:pair];
[defaults synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:JSSettingsControllerDefaultDidChangeNotification
object:pair];
}
static T get(id self, SEL sel)
{
return fk::get_value<T,SEL>([NSUserDefaults standardUserDefaults], sel);
}
};
template <typename T>
accessor_info_t accessor_info()
{
NSString *senc = [NSString stringWithFormat:@"%s%s%s%s",
@encode(void),@encode(id),
@encode(void*),@encode(T)];
NSString *genc = [NSString stringWithFormat:@"%s%s%s",
@encode(T),@encode(id),
@encode(void*)];
accessor_info_t info;
info.setter_ptr = reinterpret_cast<void*>(&defaults_implementation<T>::set);
info.getter_ptr = reinterpret_cast<void*>(&defaults_implementation<T>::get);
info.setter_enc = fk::unbox<const char*>(senc);
info.getter_enc = fk::unbox<const char *>(genc);
return info;
}
}
//
// JSSettingsControllerPrivate.h
//
// Created by Jon on 10/16/10.
// Copyright (c) 2010 Jonathan Sterling. All rights reserved.
//
#import <objc/runtime.h>
namespace jssc
{
struct accessor_info_t
{
void *setter_ptr;
void *getter_ptr;
const char *setter_enc;
const char *getter_enc;
};
template <typename T>
struct defaults_implementation;
template <typename T>
accessor_info_t accessor_info();
}
@interface JSSettingsController ()
+ (BOOL)selectorIsSetter:(SEL)aSelector;
+ (BOOL)selector:(SEL)aSelector isSetterForProperty:(NSString *)aProperty;
+ (BOOL)selector:(SEL)aSelector isGetterForProperty:(NSString *)aProperty;
+ (objc_property_t)propertyForSelector:(SEL)aSelector;
+ (NSString *)keyFromSetter:(SEL)aSetter;
@end
@jonsterling
Copy link
Author

Then, just subclass JSSettingsController, add a singleton if you want, and add some properties:

@interface MySettingsController : JSSettingsController
@property (readwrite) NSInteger something;
@property (copy) NSString *aString;
@end

@implementation MySettingsController
@dynamic something;
@dynamic aString;
@end

And you're ready to go! You can do things like the following:

aSettingsController.something = 123;
NSLog(@"something: %i", aSettingsController.something);
NSLog(@"from NSUserDefaults", %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"something"]);
// => 123
// => 123

JSSettingsController supports the following types:

  • id
  • int
  • double
  • float
  • BOOL
  • CGPoint
  • CGRect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment