Created
July 24, 2010 22:04
-
-
Save jonsterling/489017 to your computer and use it in GitHub Desktop.
JSSettingsController
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
| // | |
| // 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; |
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
| // | |
| // 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; | |
| } | |
| } |
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
| // | |
| // 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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Then, just subclass
JSSettingsController, add a singleton if you want, and add some properties:And you're ready to go! You can do things like the following:
JSSettingsControllersupports the following types:idintdoublefloatBOOLCGPointCGRect