From 9aaa668109fb561987ecc91e0a197e979c5f2452 Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Mon, 28 Jan 2013 16:38:11 +0000 Subject: [PATCH] Version 0.9.0. See changelog. --- CHANGELOG | 4 + Example/AppDelegate.m | 4 +- Example/ExampleTests/ExampleTests.m | 13 +- Example/GVUserDefaults+Properties.h | 3 + Example/GVUserDefaults+Properties.m | 14 +- Example/ViewController.m | 118 ++++++++++++--- GVUserDefaults.podspec | 2 +- GVUserDefaults/GVUserDefaults.m | 217 ++++++++++++++++++++++++---- README.md | 12 +- 9 files changed, 325 insertions(+), 62 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b6af0e2..e6de344 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.9.0 - January 28, 2013 +- Support primitive types (int, float, bool, double) +- Improved performance due to accessor creation on start up + 0.4.1 - January 7, 2013 - Fix endless loop if no transformKey: method was present diff --git a/Example/AppDelegate.m b/Example/AppDelegate.m index 1de73ee..31cc92a 100644 --- a/Example/AppDelegate.m +++ b/Example/AppDelegate.m @@ -13,8 +13,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Removing it for debugging, starting with a clean slate every time - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserName"]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserId"]; + NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier]; + [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; diff --git a/Example/ExampleTests/ExampleTests.m b/Example/ExampleTests/ExampleTests.m index 3fcf36d..6fbd6d4 100644 --- a/Example/ExampleTests/ExampleTests.m +++ b/Example/ExampleTests/ExampleTests.m @@ -14,32 +14,37 @@ - (void)setUp { [super setUp]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserName"]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserId"]; + NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier]; + [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain]; } - (void)tearDown { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserName"]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserId"]; + NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier]; + [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain]; [super tearDown]; } - (void)testDefaults { STAssertEqualObjects([GVUserDefaults standardUserDefaults].userName, @"default", nil); STAssertEqualObjects([GVUserDefaults standardUserDefaults].userId, @1, nil); + STAssertEquals([GVUserDefaults standardUserDefaults].integerValue, 123, nil); + STAssertEquals([GVUserDefaults standardUserDefaults].boolValue, YES, nil); } - (void)testSetters { [GVUserDefaults standardUserDefaults].userName = @"changed"; [GVUserDefaults standardUserDefaults].userId = @2; + [GVUserDefaults standardUserDefaults].integerValue = 456; STAssertEqualObjects([GVUserDefaults standardUserDefaults].userName, @"changed", nil); STAssertEqualObjects([GVUserDefaults standardUserDefaults].userId, @2, nil); + STAssertEquals([GVUserDefaults standardUserDefaults].integerValue, 456, nil); } - (void)testGetters { STAssertEqualObjects([GVUserDefaults standardUserDefaults].userName, [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserName"], nil); STAssertEqualObjects([GVUserDefaults standardUserDefaults].userId, [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserId"], nil); + STAssertEquals([GVUserDefaults standardUserDefaults].integerValue, [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultIntegerValue"], nil); } @end diff --git a/Example/GVUserDefaults+Properties.h b/Example/GVUserDefaults+Properties.h index f394f79..8494069 100644 --- a/Example/GVUserDefaults+Properties.h +++ b/Example/GVUserDefaults+Properties.h @@ -12,5 +12,8 @@ @property (nonatomic, weak) NSString *userName; @property (nonatomic, weak) NSNumber *userId; +@property (nonatomic) NSInteger integerValue; +@property (nonatomic) BOOL boolValue; +@property (nonatomic) float floatValue; @end diff --git a/Example/GVUserDefaults+Properties.m b/Example/GVUserDefaults+Properties.m index b807cb4..c1faf2a 100644 --- a/Example/GVUserDefaults+Properties.m +++ b/Example/GVUserDefaults+Properties.m @@ -10,10 +10,19 @@ @implementation GVUserDefaults (Properties) +@dynamic userName; +@dynamic userId; +@dynamic integerValue; +@dynamic boolValue; +@dynamic floatValue; + - (NSDictionary *)setupDefaults { return @{ @"userName": @"default", - @"userId": @1 + @"userId": @1, + @"integerValue": @123, + @"boolValue": @YES, + @"floatValue": @12.3, }; } @@ -22,7 +31,4 @@ return [NSString stringWithFormat:@"NSUserDefault%@", key]; } -@dynamic userName; -@dynamic userId; - @end diff --git a/Example/ViewController.m b/Example/ViewController.m index 6462459..eeb212e 100644 --- a/Example/ViewController.m +++ b/Example/ViewController.m @@ -15,28 +15,106 @@ - (void)viewDidLoad { [super viewDidLoad]; - NSString *test; - - LogTimestamp; - - test = [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserName"]; - - LogTimestamp; - - test = [GVUserDefaults standardUserDefaults].userName; - NSLog(@"userName: %@", test); - - LogTimestamp; - - [[NSUserDefaults standardUserDefaults] setObject:@"Hello!" forKey:@"NSUSerDefaultUserName"]; - - LogTimestamp; - - [GVUserDefaults standardUserDefaults].userName = @"Hello!"; - - LogTimestamp; + NSLog(@"Should be null:"); + NSLog(@"userName: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserName"]); + NSLog(@"--------------------"); + NSLog(@"Should be default:"); NSLog(@"userName: %@", [GVUserDefaults standardUserDefaults].userName); + NSLog(@"--------------------"); + + [[NSUserDefaults standardUserDefaults] setObject:@"test1" forKey:@"NSUserDefaultUserName"]; + + NSLog(@"Should all be test1:"); + NSLog(@"userName: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserName"]); + NSLog(@"userName: %@", [GVUserDefaults standardUserDefaults].userName); + NSLog(@"--------------------"); + + [GVUserDefaults standardUserDefaults].userName = @"Test 2"; + + NSLog(@"Should all be test2:"); + NSLog(@"userName: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserName"]); + NSLog(@"userName: %@", [GVUserDefaults standardUserDefaults].userName); + NSLog(@"--------------------"); + + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultUserName"]; + + NSLog(@"Should all be default:"); + NSLog(@"userName: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultUserName"]); + NSLog(@"userName: %@", [GVUserDefaults standardUserDefaults].userName); + NSLog(@"--------------------"); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + NSLog(@"Should all be 123:"); + NSLog(@"integerValue: %i", [GVUserDefaults standardUserDefaults].integerValue); + NSLog(@"integerValue: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultIntegerValue"]); + NSLog(@"integerValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultIntegerValue"]); + NSLog(@"--------------------"); + + [GVUserDefaults standardUserDefaults].integerValue = 789; + + NSLog(@"Should all be 789:"); + NSLog(@"integerValue: %i", [GVUserDefaults standardUserDefaults].integerValue); + NSLog(@"integerValue: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultIntegerValue"]); + NSLog(@"integerValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultIntegerValue"]); + NSLog(@"--------------------"); + + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultIntegerValue"]; + + NSLog(@"Should all be 123:"); + NSLog(@"integerValue: %i", [GVUserDefaults standardUserDefaults].integerValue); + NSLog(@"integerValue: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultIntegerValue"]); + NSLog(@"integerValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultIntegerValue"]); + NSLog(@"--------------------"); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + NSLog(@"Should all be 1:"); + NSLog(@"boolValue: %i", [GVUserDefaults standardUserDefaults].boolValue); + NSLog(@"boolValue: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultBoolValue"]); + NSLog(@"boolValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultBoolValue"]); + NSLog(@"--------------------"); + + [GVUserDefaults standardUserDefaults].boolValue = NO; + + NSLog(@"Should all be 0:"); + NSLog(@"boolValue: %i", [GVUserDefaults standardUserDefaults].boolValue); + NSLog(@"boolValue: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultBoolValue"]); + NSLog(@"boolValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultBoolValue"]); + NSLog(@"--------------------"); + + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultBoolValue"]; + + NSLog(@"Should all be 1:"); + NSLog(@"boolValue: %i", [GVUserDefaults standardUserDefaults].boolValue); + NSLog(@"boolValue: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"NSUserDefaultBoolValue"]); + NSLog(@"boolValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultBoolValue"]); + NSLog(@"--------------------"); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + NSLog(@"Should all be 12.3:"); + NSLog(@"floatValue: %f", [GVUserDefaults standardUserDefaults].floatValue); + NSLog(@"floatValue: %f", [[NSUserDefaults standardUserDefaults] floatForKey:@"NSUserDefaultFloatValue"]); + NSLog(@"floatValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultFloatValue"]); + NSLog(@"--------------------"); + + [GVUserDefaults standardUserDefaults].floatValue = 66.6; + + NSLog(@"Should all be 66.6:"); + NSLog(@"floatValue: %f", [GVUserDefaults standardUserDefaults].floatValue); + NSLog(@"floatValue: %f", [[NSUserDefaults standardUserDefaults] floatForKey:@"NSUserDefaultFloatValue"]); + NSLog(@"floatValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultFloatValue"]); + NSLog(@"--------------------"); + + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"NSUserDefaultFloatValue"]; + + NSLog(@"Should all be 12.3:"); + NSLog(@"floatValue: %f", [GVUserDefaults standardUserDefaults].floatValue); + NSLog(@"floatValue: %f", [[NSUserDefaults standardUserDefaults] floatForKey:@"NSUserDefaultFloatValue"]); + NSLog(@"floatValue OBJECT: %@", [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserDefaultFloatValue"]); + NSLog(@"--------------------"); } @end diff --git a/GVUserDefaults.podspec b/GVUserDefaults.podspec index b8da904..021457a 100644 --- a/GVUserDefaults.podspec +++ b/GVUserDefaults.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "GVUserDefaults" - s.version = "0.4.1" + s.version = "0.9.0" s.summary = "NSUserDefaults access via properties." s.homepage = "https://github.com/gangverk/GVUserDefaults" s.license = 'MIT' diff --git a/GVUserDefaults/GVUserDefaults.m b/GVUserDefaults/GVUserDefaults.m index cd949cf..852f829 100644 --- a/GVUserDefaults/GVUserDefaults.m +++ b/GVUserDefaults/GVUserDefaults.m @@ -9,8 +9,104 @@ #import "GVUserDefaults.h" #import +@interface GVUserDefaults () +@property (strong, nonatomic) NSMutableDictionary *mapping; +@end + @implementation GVUserDefaults +enum TypeEncodings { + Char = 'c', + Short = 's', + Int = 'i', + Long = 'l', + LongLong = 'q', + UnsignedChar = 'C', + UnsignedShort = 'S', + UnsignedInt = 'I', + UnsignedLong = 'L', + UnsignedLongLong = 'Q', + Float = 'f', + Double = 'd', + Object = '@' +}; + +- (NSString *)defaultsKeyForPropertyNamed:(char const *)propertyName { + NSString *key = [NSString stringWithFormat:@"%s", propertyName]; + return [self _transformKey:key]; +} + +- (NSString *)defaultsKeyForSelector:(SEL)selector { + return [self.mapping objectForKey:NSStringFromSelector(selector)]; +} + +static long long longLongGetter(GVUserDefaults *self, SEL _cmd) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + return [[[NSUserDefaults standardUserDefaults] objectForKey:key] longLongValue]; +} + +static void longLongSetter(GVUserDefaults *self, SEL _cmd, long long value) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + NSNumber *object = [NSNumber numberWithLongLong:value]; + [[NSUserDefaults standardUserDefaults] setObject:object forKey:key]; +} + +static bool boolGetter(GVUserDefaults *self, SEL _cmd) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + return [[NSUserDefaults standardUserDefaults] boolForKey:key]; +} + +static void boolSetter(GVUserDefaults *self, SEL _cmd, bool value) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + [[NSUserDefaults standardUserDefaults] setBool:value forKey:key]; +} + +static int integerGetter(GVUserDefaults *self, SEL _cmd) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + return [[NSUserDefaults standardUserDefaults] integerForKey:key]; +} + +static void integerSetter(GVUserDefaults *self, SEL _cmd, int value) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + [[NSUserDefaults standardUserDefaults] setInteger:value forKey:key]; +} + +static float floatGetter(GVUserDefaults *self, SEL _cmd) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + return [[NSUserDefaults standardUserDefaults] floatForKey:key]; +} + +static void floatSetter(GVUserDefaults *self, SEL _cmd, float value) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + [[NSUserDefaults standardUserDefaults] setFloat:value forKey:key]; +} + +static double doubleGetter(GVUserDefaults *self, SEL _cmd) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + return [[NSUserDefaults standardUserDefaults] doubleForKey:key]; +} + +static void doubleSetter(GVUserDefaults *self, SEL _cmd, double value) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + [[NSUserDefaults standardUserDefaults] setDouble:value forKey:key]; +} + +static id objectGetter(GVUserDefaults *self, SEL _cmd) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + return [[NSUserDefaults standardUserDefaults] objectForKey:key]; +} + +static void objectSetter(GVUserDefaults *self, SEL _cmd, id object) { + NSString *key = [self defaultsKeyForSelector:_cmd]; + if (object) { + [[NSUserDefaults standardUserDefaults] setObject:object forKey:key]; + } else { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; + } +} + +#pragma mark - Begin + + (GVUserDefaults *)standardUserDefaults { static dispatch_once_t pred; static GVUserDefaults *sharedInstance = nil; @@ -31,27 +127,12 @@ } [[NSUserDefaults standardUserDefaults] registerDefaults:mutableDefaults]; } + + [self generateAccessorMethods]; } return self; } -+ (BOOL)resolveInstanceMethod:(SEL)aSEL { - NSString *method = NSStringFromSelector(aSEL); - - if ([method isEqualToString:@"transformKey:"] || [method isEqualToString:@"setupDefaults"]) { - // Prevent endless loop for optional (and missing) category methods - return [super resolveInstanceMethod:aSEL]; - } - - if ([method hasPrefix:@"set"]) { - class_addMethod([self class], aSEL, (IMP) accessorSetter, "v@:@"); - return YES; - } else { - class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:"); - return YES; - } -} - - (NSString *)_transformKey:(NSString *)key { if ([self respondsToSelector:@selector(transformKey:)]) { return [self performSelector:@selector(transformKey:) withObject:key]; @@ -60,20 +141,98 @@ return key; } -id accessorGetter(GVUserDefaults *self, SEL _cmd) { - NSString *key = NSStringFromSelector(_cmd); - key = [self _transformKey:key]; - return [[NSUserDefaults standardUserDefaults] objectForKey:key]; -} +- (void)generateAccessorMethods { + unsigned int count = 0; + objc_property_t *properties = class_copyPropertyList([self class], &count); -void accessorSetter(GVUserDefaults *self, SEL _cmd, id newValue) { - NSString *method = NSStringFromSelector(_cmd); - NSString *key = [[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] stringByReplacingOccurrencesOfString:@":" withString:@""]; - key = [key stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[key substringToIndex:1] lowercaseString]]; - key = [self _transformKey:key]; + self.mapping = [NSMutableDictionary dictionary]; - // Set value of the key anID to newValue - [[NSUserDefaults standardUserDefaults] setObject:newValue forKey:key]; + for (int i = 0; i < count; ++i) { + objc_property_t property = properties[i]; + const char *name = property_getName(property); + const char *attributes = property_getAttributes(property); + + char *getter = strstr(attributes, ",G"); + if (getter) { + getter = strdup(getter + 2); + getter = strsep(&getter, ","); + } else { + getter = strdup(name); + } + SEL getterSel = sel_registerName(getter); + free(getter); + + char *setter = strstr(attributes, ",S"); + if (setter) { + setter = strdup(setter + 2); + setter = strsep(&setter, ","); + } else { + asprintf(&setter, "set%c%s:", toupper(name[0]), name + 1); + } + SEL setterSel = sel_registerName(setter); + free(setter); + + NSString *key = [self defaultsKeyForPropertyNamed:name]; + [self.mapping setValue:key forKey:NSStringFromSelector(getterSel)]; + [self.mapping setValue:key forKey:NSStringFromSelector(setterSel)]; + + IMP getterImp = NULL; + IMP setterImp = NULL; + char type = attributes[1]; + switch (type) { + case Short: + case Long: + case LongLong: + case UnsignedChar: + case UnsignedShort: + case UnsignedInt: + case UnsignedLong: + case UnsignedLongLong: + getterImp = (IMP)longLongGetter; + setterImp = (IMP)longLongSetter; + break; + + case Char: + getterImp = (IMP)boolGetter; + setterImp = (IMP)boolSetter; + break; + + case Int: + getterImp = (IMP)integerGetter; + setterImp = (IMP)integerSetter; + break; + + case Float: + getterImp = (IMP)floatGetter; + setterImp = (IMP)floatSetter; + break; + + case Double: + getterImp = (IMP)doubleGetter; + setterImp = (IMP)doubleSetter; + break; + + case Object: + getterImp = (IMP)objectGetter; + setterImp = (IMP)objectSetter; + break; + + default: + free(properties); + [NSException raise:NSInternalInconsistencyException format:@"Unsupported type of property \"%s\" in class %@", name, self]; + break; + } + + char types[5]; + + snprintf(types, 4, "%c@:", type); + class_addMethod([self class], getterSel, getterImp, types); + + snprintf(types, 5, "v@:%c", type); + class_addMethod([self class], setterSel, setterImp, types); + } + + free(properties); } @end diff --git a/README.md b/README.md index 8035b71..ac58a55 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,18 @@ Create a category on `GVUserDefaults`, add some properties in the .h file and ma @interface GVUserDefaults (Properties) @property (nonatomic, weak) NSString *userName; @property (nonatomic, weak) NSNumber *userId; + @property (nonatomic) NSInteger integerValue; + @property (nonatomic) BOOL boolValue; + @property (nonatomic) float floatValue; @end // .m @implementation GVUserDefaults (Properties) @dynamic userName; @dynamic userId; + @dynamic integerValue; + @dynamic boolValue; + @dynamic floatValue; @end Now, instead of using `[[NSUserDefaults standardUserDefaults] objectForKey:@"userName"]`, you can simply use `[GVUserDefaults standardUserDefaults].userName`. @@ -23,8 +29,6 @@ You can even save defaults by setting the property: [GVUserDefaults standardUserDefaults].userName = @"myusername"; -### Objects only -At this moment only objects can be stored, so no integers or booleans. Just wrap them in an NSNumber. ### Key prefix The keys in NSUserDefaults are the same name as your properties. If you'd like to prefix or alter them, add a `transformKey:` method to your category. For example, to turn "userName" into "NSUserDefaultUserName": @@ -78,3 +82,7 @@ GVUserDefaults is an open source project and your contribution is very much appr ## License GVUserDefaults is available under the MIT license. See the LICENSE file for more info. + + +## Thanks +A huge thank you goes to [ADVUserDefaults](https://github.com/advantis/ADVUserDefaults) for its method of creating accessors for primitive types. \ No newline at end of file