// // PSDefaults.m // Perforce // // Created by Adam Czubernat on 13.12.2013. // Copyright (c) 2013 Perforce Software, Inc. All rights reserved. // #import "PSDefaults.h" @interface PSDefaults () { id root; NSString *rootKey; NSString *relativeKeyPath; } - (void)load; - (void)registerObserving; - (void)unregisterObserving; - (NSString *)keyPathForProperty:(NSString *)property; - (NSArray *)properties; @end @implementation PSDefaults - (id)init { self = [super init]; if (self) { [self load]; [self registerObserving]; } return self; } - (void)dealloc { [self unregisterObserving]; } #pragma mark - Public + (instancetype)sharedInstance { static char sharedInstanceKey; @synchronized (self) { id sharedInstance = objc_getAssociatedObject(self, &sharedInstanceKey); if (!sharedInstance) objc_setAssociatedObject(self, &sharedInstanceKey, sharedInstance = [[self alloc] init], OBJC_ASSOCIATION_RETAIN); return sharedInstance; } } - (NSString *)keyPath { return self.className; } #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; id value = [change objectForKey:NSKeyValueChangeNewKey]; keyPath = [self keyPathForProperty:keyPath]; if (value != [NSNull null]) { [root setValue:value forKeyPath:keyPath]; } else { // Removing value because NSUserdefaults can't store NSNull id parent = root; NSRange range = [keyPath rangeOfString:@"." options:NSBackwardsSearch]; if (range.location != NSNotFound) { parent = [root valueForKeyPath:[keyPath substringToIndex:range.location]]; keyPath = [keyPath substringFromIndex:range.location+1]; } [parent removeObjectForKey:keyPath]; } if (rootKey) [defaults setObject:root forKey:rootKey]; [defaults synchronize]; } #pragma mark - Private - (void)load { rootKey = self.keyPath; NSRange range = [rootKey rangeOfString:@"."]; if (range.location != NSNotFound) { relativeKeyPath = [rootKey substringFromIndex:range.location+1]; rootKey = [rootKey substringToIndex:range.location]; } NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if (rootKey) root = [NSMutableDictionary dictionaryWithDictionary:[defaults objectForKey:rootKey]]; else root = defaults; // Create mutable tree from root element NSMutableDictionary *element = root; for (NSString *key in [relativeKeyPath componentsSeparatedByString:@"."]) { id value = [NSMutableDictionary dictionaryWithDictionary:[element objectForKey:key]]; [element setObject:value forKey:key]; element = value; } // Load defaults to own properties for (NSString *property in [self properties]) { id value = [root valueForKeyPath:[self keyPathForProperty:property]]; if (value) { if ([value isKindOfClass:[NSNull class]]) value = nil; [self setValue:value forKey:property]; } } } - (void)registerObserving { for (NSString *property in [self properties]) [self addObserver:self forKeyPath:property options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL]; } - (void)unregisterObserving { for (NSString *property in [self properties]) [self removeObserver:self forKeyPath:property]; } - (NSString *)keyPathForProperty:(NSString *)property { if (relativeKeyPath) return [relativeKeyPath stringByAppendingFormat:@".%@", property]; return property; } - (NSArray *)properties { NSMutableArray *properties = [NSMutableArray array]; unsigned int count; objc_property_t *propertyList = class_copyPropertyList([self class], &count); for (unsigned int idx=0; idx