Commits

Peter Hosey committed 09efdfc Draft

Created a branch with a tentative iCloud pref-syncing implementation.

Comments (0)

Files changed (7)

CPU Usage.xcodeproj/project.pbxproj

 		07EE02490A4A2BB200A31AED /* CPUUsageMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 07EE02480A4A2BB200A31AED /* CPUUsageMonitor.m */; };
 		07EE02970A4A4F0E00A31AED /* CPUUsageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 07EE02960A4A4F0E00A31AED /* CPUUsageView.m */; };
 		07FA8A990A4E697500960E0E /* NSString+Percentage.m in Sources */ = {isa = PBXBuildFile; fileRef = 07FA8A980A4E697500960E0E /* NSString+Percentage.m */; };
+		3109C2F21485E1B90035A276 /* PRHUbiquitousUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 3109C2F11485E1B90035A276 /* PRHUbiquitousUserDefaults.m */; };
 		3111B73F13C431940096D947 /* Warnings.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 3111B73E13C431940096D947 /* Warnings.xcconfig */; };
 		312281EB13DD9885003FE667 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 312281E913DD9885003FE667 /* MainMenu.xib */; };
 		316719580CAC026C00E3F695 /* SnappingWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 316719570CAC026C00E3F695 /* SnappingWindow.m */; };
+		3186C9CD148625F800060FEA /* PRHUbiquitousUserDefaultsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3186C9CC148625F800060FEA /* PRHUbiquitousUserDefaultsController.m */; };
 		31DFF5260C28D9CF00EBBBCC /* PRHGradientView.m in Sources */ = {isa = PBXBuildFile; fileRef = 31DFF5250C28D9CF00EBBBCC /* PRHGradientView.m */; };
 		8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
 		8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
 		29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
 		29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
 		29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
+		3109C2F01485E1B90035A276 /* PRHUbiquitousUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PRHUbiquitousUserDefaults.h; sourceTree = "<group>"; };
+		3109C2F11485E1B90035A276 /* PRHUbiquitousUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PRHUbiquitousUserDefaults.m; sourceTree = "<group>"; };
 		3111B73E13C431940096D947 /* Warnings.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
 		312281EA13DD9885003FE667 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
 		316719560CAC026C00E3F695 /* SnappingWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SnappingWindow.h; sourceTree = "<group>"; };
 		316719570CAC026C00E3F695 /* SnappingWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SnappingWindow.m; sourceTree = "<group>"; };
+		3186C9CB148625F800060FEA /* PRHUbiquitousUserDefaultsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PRHUbiquitousUserDefaultsController.h; sourceTree = "<group>"; };
+		3186C9CC148625F800060FEA /* PRHUbiquitousUserDefaultsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PRHUbiquitousUserDefaultsController.m; sourceTree = "<group>"; };
 		31DFF5240C28D9CF00EBBBCC /* PRHGradientView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PRHGradientView.h; sourceTree = "<group>"; };
 		31DFF5250C28D9CF00EBBBCC /* PRHGradientView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PRHGradientView.m; sourceTree = "<group>"; };
 		31E3FEDE146E592800A7A215 /* CPU Usage.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "CPU Usage.entitlements"; sourceTree = "<group>"; };
 				316719550CAC026C00E3F695 /* SnappingWindow */,
 				31DFF5240C28D9CF00EBBBCC /* PRHGradientView.h */,
 				31DFF5250C28D9CF00EBBBCC /* PRHGradientView.m */,
+				3109C2F01485E1B90035A276 /* PRHUbiquitousUserDefaults.h */,
+				3109C2F11485E1B90035A276 /* PRHUbiquitousUserDefaults.m */,
+				3186C9CB148625F800060FEA /* PRHUbiquitousUserDefaultsController.h */,
+				3186C9CC148625F800060FEA /* PRHUbiquitousUserDefaultsController.m */,
 			);
 			name = Classes;
 			sourceTree = "<group>";
 				0720203C0A579239007BC356 /* BZGridEnumerator.m in Sources */,
 				31DFF5260C28D9CF00EBBBCC /* PRHGradientView.m in Sources */,
 				316719580CAC026C00E3F695 /* SnappingWindow.m in Sources */,
+				3109C2F21485E1B90035A276 /* PRHUbiquitousUserDefaults.m in Sources */,
+				3186C9CD148625F800060FEA /* PRHUbiquitousUserDefaultsController.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_ENTITLEMENTS = "CPU Usage.entitlements";
-				CODE_SIGN_IDENTITY = "Mac Developer: Peter Hosey (LTP7TXW997)";
+				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
 				GCC_DYNAMIC_NO_PIC = YES;
 				GCC_MODEL_TUNING = G5;
 				INSTALL_PATH = "$(HOME)/Applications";
 				OTHER_LDFLAGS_QUOTED_FOR_TARGET_1 = "-F\"$(CONFIGURATION_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)\"";
 				PRODUCT_NAME = "CPU Usage";
-				PROVISIONING_PROFILE = "27B721BE-A05B-40E0-87C1-69FB9701544A";
 				WRAPPER_EXTENSION = app;
 				ZERO_LINK = YES;
 			};
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CODE_SIGN_ENTITLEMENTS = "CPU Usage.entitlements";
-				CODE_SIGN_IDENTITY = "Mac Developer: Peter Hosey (LTP7TXW997)";
+				CODE_SIGN_IDENTITY = "";
 				GCC_DYNAMIC_NO_PIC = YES;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_MODEL_TUNING = G5;
 				INSTALL_PATH = "$(HOME)/Applications";
 				OTHER_LDFLAGS_QUOTED_FOR_TARGET_1 = "-F\"$(CONFIGURATION_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)\"";
 				PRODUCT_NAME = "CPU Usage";
-				PROVISIONING_PROFILE = "27B721BE-A05B-40E0-87C1-69FB9701544A";
 				WRAPPER_EXTENSION = app;
 			};
 			name = Release;

CPUUsageMonitor.m

 
 #import "BZGridEnumerator.h"
 #import "SnappingWindow.h"
+#import "PRHUbiquitousUserDefaults.h"
+#import "PRHUbiquitousUserDefaultsController.h"
 
 #include <sys/types.h>
 //sqrtf, ceilf
 	NSUInteger threadsRemainingToDie;
 	BOOL threadsShouldExit;
 
+	PRHUbiquitousUserDefaults *defaults;
+
 	//Saved preferences, for restoration after Cancel.
 	NSColor *savedBackgroundColor;
 	NSSize   savedCellSize;
 			[NSNumber numberWithBool:NO], DOCK_ICON_ENABLED_PREF,
 			nil];
 
-		[[NSUserDefaults standardUserDefaults] registerDefaults:defaultPrefs];
+		self->defaults = (PRHUbiquitousUserDefaults *)[[[PRHUbiquitousUserDefaultsController sharedUserDefaultsController] defaults] retain];
+		[self->defaults registerDefaults:defaultPrefs];
 	}
 	return self;
 }
 
 - (void)applicationDidFinishLaunching:(NSNotification *)notification {
 	//Set up us the properties of new cells.
-	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
-//	NSData *data = [defaults dataForKey:BACKGROUND_COLOR_PREF];
+//	NSData *data = [self->defaults dataForKey:BACKGROUND_COLOR_PREF];
 //	NSColor *color = data ? [NSUnarchiver unarchiveObjectWithData:data] : [NSColor redColor];
 //	[self setBackgroundColor:color];
 
-	[self setCellWidth:[defaults floatForKey:CELL_WIDTH_PREF]];
-	[self setCellHeight:[defaults floatForKey:CELL_HEIGHT_PREF]];
+	[self setCellWidth:[self->defaults floatForKey:CELL_WIDTH_PREF]];
+	[self setCellHeight:[self->defaults floatForKey:CELL_HEIGHT_PREF]];
 
 	//Sign up for notifications of pref changes.
-	NSUserDefaultsController *udc = [NSUserDefaultsController sharedUserDefaultsController];
+	NSUserDefaultsController *udc = [PRHUbiquitousUserDefaultsController sharedUserDefaultsController];
 	NSDictionary *colorBindingOptions = [NSDictionary dictionaryWithObject:@"NSUnarchiveFromData" forKey:NSValueTransformerNameBindingOption];
 	
 	[self bind:@"floaterZeroUsageTextColor"        toObject:udc withKeyPath:@"values."FLOATER_ZERO_USAGE_TEXT_COLOR_PREF         options:colorBindingOptions];
 									  options:nil];
 
 	//Fetch the orientation from prefs.
-	orientation = [defaults integerForKey:FLOATER_ORIENTATION_PREF];
+	orientation = [self->defaults integerForKey:FLOATER_ORIENTATION_PREF];
 
 	//Set up the orientation buttons.
 	int buttonStates[2] = { NSOffState, NSOnState };
 
 - (IBAction)setOrientationToVertical:sender {
 	orientation = CPUUsageOrientationVertical;
-	[[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:[NSNumber numberWithInteger:orientation] forKey:FLOATER_ORIENTATION_PREF];
+	[[[PRHUbiquitousUserDefaultsController sharedUserDefaultsController] values] setValue:[NSNumber numberWithInteger:orientation] forKey:FLOATER_ORIENTATION_PREF];
 
 	[self layOutCellsInTarget:window];
 
 }
 - (IBAction)setOrientationToHorizontal:sender {
 	orientation = CPUUsageOrientationHorizontal;
-	[[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:[NSNumber numberWithInteger:orientation] forKey:FLOATER_ORIENTATION_PREF];
+	[[[PRHUbiquitousUserDefaultsController sharedUserDefaultsController] values] setValue:[NSNumber numberWithInteger:orientation] forKey:FLOATER_ORIENTATION_PREF];
 
 	[self layOutCellsInTarget:window];
 
 }
 - (IBAction)setOrientationToRectangular:sender {
 	orientation = CPUUsageOrientationRectangular;
-	[[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:[NSNumber numberWithInteger:orientation] forKey:FLOATER_ORIENTATION_PREF];
+	[[[PRHUbiquitousUserDefaultsController sharedUserDefaultsController] values] setValue:[NSNumber numberWithInteger:orientation] forKey:FLOATER_ORIENTATION_PREF];
 
 	[self layOutCellsInTarget:window];
 

PRHUbiquitousUserDefaults.h

+//
+//  PRHUbiquitousUserDefaults.h
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-29.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface PRHUbiquitousUserDefaults : NSUserDefaults
+
+@property(copy) NSArray *ubiquitousKeys;
+
+- (NSUInteger) countOfUbiquitousKeys;
+- (NSString *) objectInUbiquitousKeysAtIndex:(NSUInteger)idx;
+- (void) insertObject:(NSString *)key inUbiquitousKeysAtIndex:(NSUInteger)idx;
+- (void) removeObjectFromUbiquitousKeysAtIndex:(NSUInteger)idx;
+- (void) replaceObjectInUbiquitousKeysAtIndex:(NSUInteger)idx withObject:(NSString *)key;
+
+//Use these if you plan to set a bunch of prefs at once. The syncs with disk and iCloud will be deferred until the outermost group ends.
+- (void) beginSynchronizationGroup;
+- (void) endSynchronizationGroup;
+
+@end

PRHUbiquitousUserDefaults.m

+//
+//  PRHUbiquitousUserDefaults.m
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-29.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+#import "PRHUbiquitousUserDefaults.h"
+
+#define DEFINE_STRING_CONSTANT_WITH_ITS_OWN_NAME_AS_ITS_VALUE(name) \
+static NSString *const name = @#name;
+
+DEFINE_STRING_CONSTANT_WITH_ITS_OWN_NAME_AS_ITS_VALUE(PRHUbiquitousUserDefaults_SavedUbiquitousKeysList);
+DEFINE_STRING_CONSTANT_WITH_ITS_OWN_NAME_AS_ITS_VALUE(PRHUbiquitousUserDefaults_LastSyncDate);
+
+@interface PRHUbiquitousUserDefaults ()
+- (id) localObjectForKey:(NSString *)key;
+- (void) setLocalObject:(id)obj forKey:(NSString *)key;
+- (id) cloudObjectForKey:(NSString *)key;
+- (void) setCloudObject:(id)obj forKey:(NSString *)key;
+
+- (void) transferCloudToLocalForKeys:(NSArray *)keys;
+
+- (void) keyValueStore:(NSUbiquitousKeyValueStore *)store didChangeExternally:(NSNotification *)notif;
+@end
+
+@implementation PRHUbiquitousUserDefaults
+{
+	NSUbiquitousKeyValueStore *kvs;
+	NSMutableArray *ubiquitousKeys;
+	id changeNotificationObserver;
+	NSUInteger synchronizationGroupCount;
+}
+
+@synthesize ubiquitousKeys;
+
+- (void) commonInit {
+	static dispatch_once_t onceToken;
+	dispatch_once(&onceToken, ^{
+		self->kvs = [[NSUbiquitousKeyValueStore defaultStore] retain];
+		__block PRHUbiquitousUserDefaults *bself = self;
+		changeNotificationObserver = [[[NSNotificationCenter defaultCenter] addObserverForName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:self->kvs queue:nil usingBlock:^(NSNotification *notif) {
+			[bself keyValueStore:[notif object] didChangeExternally:notif];
+		}] retain];
+		
+		self.ubiquitousKeys = [self objectForKey:PRHUbiquitousUserDefaults_SavedUbiquitousKeysList];
+		if (!self.ubiquitousKeys) {
+//			self.ubiquitousKeys = [NSArray arrayWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"UbiquitousPreferenceKeys" withExtension:@"plist"]];
+			if (self.ubiquitousKeys) {
+				[self setObject:self.ubiquitousKeys forKey:PRHUbiquitousUserDefaults_SavedUbiquitousKeysList];
+			}
+		}
+		
+		NSDate *lastLocalSaved = [self localObjectForKey:PRHUbiquitousUserDefaults_LastSyncDate];
+		NSDate *lastCloudSaved = [self cloudObjectForKey:PRHUbiquitousUserDefaults_LastSyncDate];
+		if ((!lastLocalSaved) || [lastLocalSaved compare:lastCloudSaved] == NSOrderedAscending) {
+			//Cloud prefs are newer.
+			[self transferCloudToLocalForKeys:self.ubiquitousKeys];
+		}
+	});
+}
+
+- (id) init {
+	NSLog(@"%s called", __func__);
+    if ((self = [super init])) {
+        [self commonInit];
+    }
+    return self;
+}
+- (id) initWithUser:(NSString *)username {
+	NSLog(@"%s called", __func__);
+	if ((self = [super initWithUser:username])) {
+        [self commonInit];
+	}
+	return self;
+}
+- (void) dealloc {
+	[self->kvs release];
+
+	[super dealloc];
+}
+
+- (NSUInteger) countOfUbiquitousKeys {
+	return [ubiquitousKeys count];
+}
+- (NSString *) objectInUbiquitousKeysAtIndex:(NSUInteger)idx {
+	return [ubiquitousKeys objectAtIndex:idx];
+}
+- (void) insertObject:(NSString *)key inUbiquitousKeysAtIndex:(NSUInteger)idx {
+	[ubiquitousKeys insertObject:key atIndex:idx];
+}
+- (void) removeObjectFromUbiquitousKeysAtIndex:(NSUInteger)idx {
+	[ubiquitousKeys removeObjectAtIndex:idx];
+}
+- (void) replaceObjectInUbiquitousKeysAtIndex:(NSUInteger)idx withObject:(NSString *)key {
+	[ubiquitousKeys replaceObjectAtIndex:idx withObject:key];
+}
+
+#pragma mark -
+
+- (id) localObjectForKey:(NSString *)key {
+	return [super objectForKey:key];
+}
+- (void) setLocalObject:(id)obj forKey:(NSString *)key {
+	[super setObject:obj forKey:key];
+}
+
+- (id) cloudObjectForKey:(NSString *)key {
+	return [self->kvs objectForKey:key];
+}
+- (void) setCloudObject:(id)obj forKey:(NSString *)key {
+	[self beginSynchronizationGroup];
+	[self->kvs setObject:obj forKey:key];
+	[self endSynchronizationGroup];
+}
+
+- (void) transferCloudToLocalForKeys:(NSArray *)keys {
+	for (NSString *key in keys) {
+		[self setLocalObject:[self cloudObjectForKey:key] forKey:key];
+	}
+}
+
+#pragma mark Synchronization groups
+
+- (void) beginSynchronizationGroup {
+	++self->synchronizationGroupCount;
+}
+- (void) endSynchronizationGroup {
+	if (0 == --self->synchronizationGroupCount) {
+		[self synchronize];
+		[self->kvs synchronize];
+	}
+}
+
+#pragma mark Overridden methods
+
+- (id) objectForKey:(NSString *)key {
+	id object = [self localObjectForKey:key];
+	if (!object) {
+		if ([self.ubiquitousKeys containsObject:key]) {
+			object = [self cloudObjectForKey:key];
+		}
+	}
+	return object;
+}
+- (void) setObject:(id)object forKey:(NSString *)key {
+	bool ubiquitous = [self.ubiquitousKeys containsObject:key];
+	if (ubiquitous) {
+		[self beginSynchronizationGroup];
+	}
+
+	[self setLocalObject:object forKey:key];
+
+	if (ubiquitous) {
+		[self setCloudObject:object forKey:key];
+
+		if (key != PRHUbiquitousUserDefaults_LastSyncDate) {
+			[self setObject:[NSDate date] forKey:PRHUbiquitousUserDefaults_LastSyncDate];
+		}
+
+		[self endSynchronizationGroup];
+	}
+}
+
+#pragma mark Notification handlers
+
+- (void) keyValueStore:(NSUbiquitousKeyValueStore *)store didChangeExternally:(NSNotification *)notif {
+	NSDictionary *userInfo = [notif userInfo];
+	NSNumber *changeReasonNum = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
+	if ([changeReasonNum integerValue] == NSUbiquitousKeyValueStoreServerChange) {
+		NSArray *changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
+		[self transferCloudToLocalForKeys:changedKeys];
+	}
+}
+
+@end

PRHUbiquitousUserDefaultsController.h

+//
+//  PRHUbiquitousUserDefaultsController.h
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-30.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+//Uses the PRHUbiquitousUserDefaults instead of [NSUserDefaults standardUserDefaults].
+
+@interface PRHUbiquitousUserDefaultsController : NSUserDefaultsController
+
+@end

PRHUbiquitousUserDefaultsController.m

+//
+//  PRHUbiquitousUserDefaultsController.m
+//  CPU Usage
+//
+//  Created by Peter Hosey on 2011-11-30.
+//  Copyright (c) 2011 Peter Hosey. All rights reserved.
+//
+
+#import "PRHUbiquitousUserDefaultsController.h"
+
+#import "PRHUbiquitousUserDefaults.h"
+
+@implementation PRHUbiquitousUserDefaultsController
+
++ (id) sharedUserDefaultsController {
+	static PRHUbiquitousUserDefaultsController *sharedInstance = nil;
+
+	static dispatch_once_t onceToken;
+	dispatch_once(&onceToken, ^{
+		sharedInstance = [[self alloc] initWithDefaults:[[[PRHUbiquitousUserDefaults alloc] init] autorelease] initialValues:[NSDictionary dictionary]];
+	});
+
+	return sharedInstance;
+}
+
+@end

en.lproj/MainMenu.xib

 			<object class="NSUserDefaultsController" id="532039615">
 				<bool key="NSSharedInstance">YES</bool>
 			</object>
+			<object class="NSCustomObject" id="189702765">
+				<string key="NSClassName">PRHUbiquitousUserDefaultsController</string>
+			</object>
 		</array>
 		<object class="IBObjectContainer" key="IBDocument.Objects">
 			<array class="NSMutableArray" key="connectionRecords">
 						<reference key="object" ref="982489564"/>
 						<reference key="parent" ref="113050291"/>
 					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">467</int>
+						<reference key="object" ref="189702765"/>
+						<reference key="parent" ref="0"/>
+					</object>
 				</array>
 			</object>
 			<dictionary class="NSMutableDictionary" key="flattenedProperties">
 				<string key="261.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="262.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="276.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="281.CustomClassName">PRHUbiquitousUserDefaultsController</string>
 				<string key="281.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="332.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="462.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="463.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="464.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+				<string key="467.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="56.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 				<string key="57.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
 			<nil key="activeLocalization"/>
 			<dictionary class="NSMutableDictionary" key="localizations"/>
 			<nil key="sourceID"/>
-			<int key="maxID">465</int>
+			<int key="maxID">469</int>
 		</object>
 		<object class="IBClassDescriber" key="IBDocument.Classes">
 			<array class="NSMutableArray" key="referencedPartialClassDescriptions">
 						<string key="minorKey">./Classes/PRHGradientView.h</string>
 					</object>
 				</object>
+				<object class="IBPartialClassDescription">
+					<string key="className">PRHUbiquitousUserDefaultsController</string>
+					<string key="superclassName">NSUserDefaultsController</string>
+					<object class="IBClassDescriptionSource" key="sourceIdentifier">
+						<string key="majorKey">IBProjectSource</string>
+						<string key="minorKey">./Classes/PRHUbiquitousUserDefaultsController.h</string>
+					</object>
+				</object>
 			</array>
 		</object>
 		<int key="IBDocument.localizationMode">0</int>
 			<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
 			<real value="1060" key="NS.object.0"/>
 		</object>
-		<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
-			<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
-			<real value="1060" key="NS.object.0"/>
-		</object>
 		<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
 		<int key="IBDocument.defaultPropertyAccessControl">3</int>
 		<dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.