Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize persistence of feature flags #93

Merged
merged 19 commits into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Darkly/LDDataManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extern int const kUserCacheSize;
-(void) createCustomEvent: (NSString *)eventKey withCustomValuesDictionary: (NSDictionary *)customDict;
-(void) purgeOldUser: (NSMutableDictionary *)dictionary;
-(void) saveUser: (LDUserModel *) user;
-(void) saveUserDeprecated:(LDUserModel *)user __deprecated_msg("Use saveUser: instead");
-(void) deleteProcessedEvents: (NSArray *) processedJsonArray;
-(void)flushEventsDictionary;

Expand Down
62 changes: 39 additions & 23 deletions Darkly/LDDataManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,24 @@ + (id)sharedManager {
#pragma mark - users
-(void) purgeOldUser: (NSMutableDictionary *)dictionary {
if (dictionary && [dictionary count] >= kUserCacheSize) {
NSString *removalKey;
NSDate *removalDate;
for (id key in dictionary) {
LDUserModel *currentUser = [dictionary objectForKey:key];
if (currentUser) {
if (removalKey) {
NSComparisonResult result = [removalDate compare:currentUser.updatedAt];
if (result==NSOrderedDescending) {
removalKey = currentUser.key;
removalDate = currentUser.updatedAt;
}
} else {
removalKey = currentUser.key;
removalDate = currentUser.updatedAt;
}
} else {
[dictionary removeObjectForKey:removalKey];
}
}
[dictionary removeObjectForKey:removalKey];

NSArray *sortedKeys = [dictionary keysSortedByValueUsingComparator: ^(LDUserModel *user1, LDUserModel *user2) {
return [user1.updatedAt compare:user2.updatedAt];
}];

[dictionary removeObjectForKey:sortedKeys.firstObject];
}
}

-(void) saveUser: (LDUserModel *) user {
[self saveUser:user asDict:YES];
}

-(void) saveUserDeprecated:(LDUserModel *)user {
[self saveUser:user asDict:NO];
}

-(void) saveUser:(LDUserModel *)user asDict:(BOOL)asDict {
NSMutableDictionary *userDictionary = [self retrieveUserDictionary];
if (userDictionary) {
LDUserModel *resultUser = [userDictionary objectForKey:user.key];
Expand All @@ -79,7 +73,13 @@ -(void) saveUser: (LDUserModel *) user {
[userDictionary setObject:user forKey:user.key];
}
[userDictionary setObject:user forKey:user.key];
[self storeUserDictionary:userDictionary];
if (asDict) {
[self storeUserDictionary:userDictionary];
}
else{
[self deprecatedStoreUserDictionary:userDictionary];
}

}

-(LDUserModel *)findUserWithkey: (NSString *)key {
Expand All @@ -103,6 +103,16 @@ - (void)compareConfigForUser:(LDUserModel *)user withNewUser:(LDUserModel *)newU
}

- (void)storeUserDictionary:(NSDictionary *)userDictionary {
NSMutableDictionary *archiveDictionary = [[NSMutableDictionary alloc] init];
for (NSString *key in userDictionary) {
[archiveDictionary setObject:[[userDictionary objectForKey:key] dictionaryValue] forKey:key];
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:archiveDictionary forKey:kUserDictionaryStorageKey];
[defaults synchronize];
}

- (void)deprecatedStoreUserDictionary:(NSDictionary *)userDictionary {
NSMutableDictionary *archiveDictionary = [[NSMutableDictionary alloc] init];
for (NSString *key in userDictionary) {
NSData *userEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:(LDUserModel *)[userDictionary objectForKey:key]];
Expand All @@ -116,10 +126,16 @@ - (void)storeUserDictionary:(NSDictionary *)userDictionary {

- (NSMutableDictionary *)retrieveUserDictionary {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *retrievalDictionary = [[NSMutableDictionary alloc] init];
NSDictionary *encodedDictionary = [defaults objectForKey:kUserDictionaryStorageKey];
NSMutableDictionary *retrievalDictionary = [[NSMutableDictionary alloc] initWithDictionary:encodedDictionary];
for (NSString *key in encodedDictionary) {
LDUserModel *decodedUser = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)[encodedDictionary objectForKey:key]];
LDUserModel *decodedUser;
if ([[encodedDictionary objectForKey:key] isKindOfClass:[NSData class]]) {
decodedUser = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)[encodedDictionary objectForKey:key]];
}
else{
decodedUser = [[LDUserModel alloc] initWithDictionary:[encodedDictionary objectForKey:key]];
}
[retrievalDictionary setObject:decodedUser forKey:key];
}
return retrievalDictionary;
Expand Down
4 changes: 2 additions & 2 deletions Darkly/LDPollingManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ typedef enum {


+ (id)sharedInstance;
@property (nonatomic, assign) PollingState configPollingState;
@property (nonatomic, assign) PollingState eventPollingState;
@property (atomic, assign) PollingState configPollingState;
@property (atomic, assign) PollingState eventPollingState;

@property (strong, nonatomic) dispatch_source_t configTimer;
@property (nonatomic) NSTimeInterval configPollingIntervalMillis;
Expand Down
8 changes: 0 additions & 8 deletions Darkly/LDPollingManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ - (void)dealloc {
eventPollingState = POLL_STOPPED;
}

- (PollingState)configPollingState {
return configPollingState;
}

- (PollingState)eventPollingState {
return eventPollingState;
}

#pragma mark - Config Polling methods

- (void)startConfigPollTimer
Expand Down
15 changes: 13 additions & 2 deletions Darkly/LDUserModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ -(NSDictionary *)dictionaryValue{
self.avatar ? [dictionary setObject:self.avatar forKey: kAvatarKey] : nil;
self.custom ? [dictionary setObject:self.custom forKey: kCustomKey] : nil;
self.anonymous ? [dictionary setObject:[NSNumber numberWithBool: self.anonymous ] forKey: kAnonymousKey] : nil;
self.updatedAt ? [dictionary setObject:[formatter stringFromDate:self.updatedAt] forKey:kUpdatedAtKey] : nil;

NSMutableDictionary *customDict = [[NSMutableDictionary alloc] initWithDictionary:[dictionary objectForKey:kCustomKey]];
self.device ? [customDict setObject:self.device forKey:kDeviceKey] : nil;
self.os ? [customDict setObject:self.os forKey:kOsKey] : nil;

[dictionary setObject:customDict forKey:kCustomKey];

self.config.featuresJsonDictionary ? [dictionary setObject:[[self.config dictionaryValue] objectForKey:kFeaturesJsonDictionaryKey] forKey:kConfigKey] : nil;

return dictionary;
}
Expand Down Expand Up @@ -93,6 +96,11 @@ - (id)initWithCoder:(NSCoder *)decoder {
- (id)initWithDictionary:(NSDictionary *)dictionary {
if((self = [self init])) {
//Process json that comes down from server

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];

self.key = [dictionary objectForKey: kKeyKey];
self.ip = [dictionary objectForKey: kIpKey];
self.country = [dictionary objectForKey: kCountryKey];
Expand All @@ -101,9 +109,12 @@ - (id)initWithDictionary:(NSDictionary *)dictionary {
self.lastName = [dictionary objectForKey: kLastNameKey];
self.avatar = [dictionary objectForKey: kAvatarKey];
self.custom = [dictionary objectForKey: kCustomKey];
self.device = [dictionary objectForKey: kDeviceKey];
self.os = [dictionary objectForKey: kOsKey];
if (self.custom) {
self.device = [self.custom objectForKey: kDeviceKey];
self.os = [self.custom objectForKey: kOsKey];
}
self.anonymous = [[dictionary objectForKey: kAnonymousKey] boolValue];
self.updatedAt = [formatter dateFromString:[dictionary objectForKey:kUpdatedAtKey]];
self.config = [[LDFlagConfigModel alloc] initWithDictionary:[dictionary objectForKey:kConfigKey]];
}
return self;
Expand Down
18 changes: 17 additions & 1 deletion DarklyTests/Fixtures/feature_flags.json
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
{"_links":{"self":{"href":"/api/eval/users/eyJrZXkiOiAiamVmZkB0ZXN0LmNvbSJ9","type":"application/json"}},"devices.hasipad":false, "isConnected":null, "isAString":"test", "isAArray":[0,1,2], "isAObject":{"key": 0}, "isANumber":0, "isABawler":true}
{
"_links":
{
"href":"/api/eval/users/eyJrZXkiOiAiamVmZkB0ZXN0LmNvbSJ9",
"type":"application/json"
},
"devices.hasipad":false,
"isConnected":null,
"isAString":"test",
"isAArray":[0,1,2],
"isAObject":
{
"key": 0
},
"isANumber":0,
"isABawler":true
}
39 changes: 32 additions & 7 deletions DarklyTests/Models/LDUserModelTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#import <XCTest/XCTest.h>
#import "LDUserModel.h"
#import "LDDataManager.h"

@interface LDUserModelTest : XCTestCase
@end
Expand All @@ -30,14 +31,14 @@ -(void)testDictionaryValue {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];

NSString *filepath = [[NSBundle bundleForClass:[LDUserModelTest class]] pathForResource:@"feature_flags"
ofType:@"json"];
ofType:@"json"];
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfFile:filepath];
NSDictionary *serverJson = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
error:&error];

NSMutableDictionary *userDict = [[NSMutableDictionary alloc] initWithDictionary:@{ @"key": @"aKey",
@"ip": @"123.456.789",
Expand All @@ -52,13 +53,13 @@ -(void)testDictionaryValue {
@"device": @"iPad",
@"os": @"IOS 9.2.1"
}];

LDUserModel *user = [[LDUserModel alloc] initWithDictionary:userDict];

NSDictionary *userDict2 = [user dictionaryValue];

[userDict setObject:[[NSDictionary alloc] initWithObjects:@[@"iPad",@"IOS 9.2.1"] forKeys:@[@"device",@"os"]] forKey:@"custom"];
[userDict removeObjectsForKeys:@[@"device",@"os",@"config"]];
[userDict removeObjectsForKeys:@[@"device",@"os"]];

NSArray *allKeys = [[userDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
NSArray *allKeys2 = [[userDict2 allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
Expand All @@ -81,13 +82,37 @@ -(void)testDictionaryValue {
XCTAssertEqual([updateAtDate compare:[userDict objectForKey:@"updatedAt"]], NSOrderedSame);

NSDictionary *config2 = [userDict2 objectForKey: @"config"];

NSArray *originalKeys = [[serverJson objectForKey:@"items"] allKeys];
NSArray *configKeys = [[config2 objectForKey:@"featuresJsonDictionary"] allKeys];

XCTAssertFalse([originalKeys isEqualToArray:configKeys]);

NSLog(@"Stop");
}

-(void)testUserBackwardsCompatibility {

NSMutableDictionary *userDict = [[NSMutableDictionary alloc] initWithDictionary:@{ @"key": @"aKey",
@"ip": @"123.456.789",
@"country": @"USA",
@"firstName": @"John",
@"lastName": @"Doe",
@"email": @"[email protected]",
@"avatar": @"foo",
@"custom": @{@"foo": @"Foo"},
@"anonymous": @1,
@"device": @"iPad",
@"os": @"IOS 9.2.1"
}];

LDUserModel *user = [[LDUserModel alloc] initWithDictionary:userDict];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[LDDataManager sharedManager] saveUserDeprecated:user];
#pragma clang diagnostic pop
[[LDDataManager sharedManager] saveUser:user];

}

@end