NSUserDefaults not writing to disk - objective-c

I have a small background Mac (menubar) app which uses preferences for one simple value (string). When I launch the app from Xcode, it will almost always report the pref as being set, even when the plist does not exist in ~/Library/Preferences. Without making any codebase changes, it appears to occassionally write the prefs file upon subsequent launches. It does have a helper app which runs normally, and a Today extension, if it matters. I've tried using the [defaults synchronize] command, it doesn't do anything. I have other apps which work fine. I don't access or set NSUSerDefaults (including bindings) anywhere else in my project
Currently, my app is NOT sandboxed.
some sample code:
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults synchronize];
NSLog(#"value for key is %#", [standardDefaults valueForKey:#"firstTime"]);
//this always reports back as "Accepted" even when no pref exists
if ([[standardDefaults valueForKey:#"first"] isEqualToString:#"Accepted"])
{
NSLog(#"accepted");
//always seen
}
else
{
//rarely seen except in complete random times
NSLog(#"first time");
#try
{
NSLog(#"alert");
[standardDefaults setObject:#"Accepted" forKey:#"first"];
[standardDefaults synchronize];
}
#catch (NSException *exception)
{
NSLog(#"exception is %#", exception);
}
#finally
{
}
}
UDPATE: I now see this on new apps, non-sandboxed. Bizarre. I created a brand-new mac/Cocoa app, and used this code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults synchronize];
NSLog(#"value for key is %#", [standardDefaults valueForKey:#"firstTime"]);
if (![standardDefaults valueForKey:#"firstTime"])
{
NSLog(#"settings value for key");
[standardDefaults setValue:#"Whatever" forKey:#"firstTime"];
[standardDefaults synchronize];
}
NSLog(#"value for key is %#", [standardDefaults valueForKey:#"firstTime"]);
// Do any additional setup after loading the view.
}
The first time, it reports as missing. Every attempt afterwards, tt immediately registers as the pref existing and reporting as "whatever", even when the pref file is most definitely not there.

The file is normally periodically written by cfprefsd. All times you read/write the values you're actually talking to cfprefsd, not to the file on disk.
When you synchronize, it causes cfprefsd to get the values that you've set, again, not guaranteeing that it is written to disk.
The condition you've stated seems to indicate that the preference was set the first time round, and even though you've not seen the file appear on disk, it's reading the values. This is the expected behaviour.
If you want to test resetting the preferences before writing, you need to clear them out, using something like:
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
I believe that since the introduction of cloud preference syncing, it's become a more noticeable issue that the file doesn't appear. Modifying the file has never been considered stable.
If you want to delete the preferences from the command line, you should use defaults delete (I don't have a mac at hand to give you the exact command line for this, though).

Related

How to keep information from reseting

I have a check box type application, kinda like notes on the iPhone. When the user closes the application and double tabs the home button and deletes the application from the background, the things they have typed also get deleted or reset. I was thinking of calling
[self saveContext];
But that didn't work. WHat should I be calling?
You can usual use something such as NSUserDefaults to save information. Or, you can go another route and save the information in a data structure and write that to a file. There are many easy ways of doing this. For instance, if you want to preserve a checked value, you could do the following:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:"checked"];
[defaults synchronize];
Very simple, and then can be queried across app exits, like so:
if( [defaults boolForKey:"checked"] ) {
//do stuff here
}

Reload contents at startup

I've been banging my head against the wall for several days trying to understand how to perform an action as soon as the application starts.
Basically I want to download a plist from my website if the user turns on a switch that determines if he wants to download new contents at startup.
Point is that:
"A" class has the method to reload the contents;
"B" class has the switch that, if turned on, tells the delegate to perform the reload contents method as soon as the application starts
Now, I don't know how to tell the AppDelegate to run the method of class "A" if the switch of class "B" is turned on. Obviously I need to use NSUserDefaults, but i'm pretty lost after that.
Can anyone make things clearer? Or, is there a more comfortable workaround to do it?
yes you can do this using NSUserDefaults
in your class b.
-(void)swithChanged
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//check if !null
if(![[defaults objectForKey:#"shouldDownload"]isKindOfClass:[NSNull class]]){
if([(NSNumber*)[defaults objectForKey:#"shouldDownload"]boolValue])
{
[defaults setObject:[NSNumber numberWithInt:0] forKey:#"shouldDownload"];
[defaults synchronize];
}else{
[defaults setObject:[NSNumber numberWithInt:1] forKey:#"shouldDownload"];
[defaults synchronize];
}
}else{
//set your NSUserDefault here for the first time
}
}
in your AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//check if !null
if(![[defaults objectForKey:#"shouldDownload"]isKindOfClass:[NSNull class]]){
if([(NSNumber*)[defaults objectForKey:#"shouldDownload"]boolValue])
{
//you can write the downloadData method in this appDelegate,
//[self downloadData]
//OR
AClass *aClass = [AClass alloc]init];
[aClass downloadData];
}else{
//do not download
}
}else{
//the default behaviour of app, download or not?
}
}
Here's a post that could help you understand the flows during application start-up:
http://www.cocoanetics.com/2010/07/understanding-ios-4-backgrounding-and-delegate-messaging
Also, check this post:
applicationWillEnterForeground vs. applicationDidBecomeActive, applicationWillResignActive vs. applicationDidEnterBackground

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

How to know if window is minimizable when titlebar was double clicked?

This image is from SystemPreferences > Appearance
I want to know How do I get that value programmatically?
I ask because I am drawing a window with a customized titlebar and I want it to resemble (in behavior) as much as possible to normal (non-customized) cocoa windows.
Maybe a terminal command I can pipe or is there an cocoa API that does this?
EDIT:
Answer (thanks to NSGod)
- (void)mouseUp:(NSEvent *)event{
if ([event clickCount] == 2) {
//Get settings from "System Preferences" > "Appearance" > "Double-click on windows title bar to minimize"
NSString *const MDAppleMiniaturizeOnDoubleClickKey = #"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
BOOL shouldMiniaturize = [[userDefaults objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];
if (shouldMiniaturize) {
[self miniaturize:self];
}
}
}
Later I found that Appearance (Aqua/Graphite) can be found:
NSString * const kAppleAquaColorVariant = #"AppleAquaColorVariant";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
NSNumber *color = [userDefaults objectForKey:kAppleAquaColorVariant];
if ([color intValue] == 6) {//graphite is 6
imageName = [imageName stringByAppendingFormat:#"_graphite"];
}else{//defaults to aqua, (aqua is 1)
imageName = [imageName stringByAppendingFormat:#"_colorsryg"];
}
Which can be helpful too :)
The way I would do it is probably read the value in from user defaults.
NSString * const MDAppleMiniaturizeOnDoubleClickKey = #"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
NSNumber *miniaturize = [userDefaults
objectForKey:MDAppleMiniaturizeOnDoubleClickKey];
NSLog(#"AppleMiniaturizeOnDoubleClick == %#",
([miniaturize boolValue] ? #"YES" : #"NO"));
(This preference setting is stored in the invisible .GlobalPreferences.plist in your ~/Library/Preferences/ folder).
Note that by default, the "double-click to minimize" option is turned off, so if you check for the presence of the AppleMiniaturizeOnDoubleClick and it returns nil, that means it is off. (User defaults only start to store values if they differ from the defaults).
This key is the same in Leopard as it is in Snow Leopard. (It hasn't changed in Lion or Mountain Lion either).
Of course, there is a secret (private) method in NSWindow, -(BOOL)_shouldMiniaturizeOnDoubleClick, but I wouldn't recommend using private methods.
[UPDATE] Regarding Catfish_Man's comment: you are correct in that the line [userDefaults addSuiteNamed:NSGlobalDomain]; is unnecessary, as NSUserDefaults already has the ability to read global preferences. (I modified the code above to reflect this).
"Additionally, NSGlobalDomain is not translated to
.GlobalPreferences.plist for that method."
I'm not sure I follow you there. NSUserDefaults is built on top of CFPreferences which defines the following 6 constants:
Application:
kCFPreferencesAnyApplication,
kCFPreferencesCurrentApplication
Host:
kCFPreferencesAnyHost,
kCFPreferencesCurrentHost
User:
kCFPreferencesAnyUser,
kCFPreferencesCurrentUser
Given a fictional application bundle identifier of "com.markdouma.App" and a single host (based on your current network location that won't change for this example), there are generally 8 locations where preference information could be stored on your disk. (NOTE: The paths shown are for demonstration purposes only; the exact file path locations are subject to change). The 8 different locations arise from the different combination of the CFPreferences constants:
/Library/Preferences/.GlobalPreferences.plist
(kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesAnyHost)
/Library/Preferences/com.markdouma.App.plist
(kCFPreferencesCurrentApplication, kCFPreferencesAnyUser,
kCFPreferencesAnyHost)
/Library/Preferences/ByHost/.GlobalPreferences.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
/Library/Preferences/ByHost/com.markdouma.App.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesCurrentApplication,
kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
~/Library/Preferences/.GlobalPreferences.plist
(kCFPreferencesAnyApplication, kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)
~/Library/Preferences/com.markdouma.App.plist
(kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)
~/Library/Preferences/ByHost/.GlobalPreferences.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesAnyApplication,
kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
~/Library/Preferences/ByHost/com.markdouma.App.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesCurrentApplication,
kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
While NSUserDefaults can only write to the domain combination shown in italics, it automatically has read access to the domain combinations shown in bold. In other words, without having to do anything, I can automatically run the following code and print the results:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *miniaturize = [userDefaults
objectForKey:#"AppleMiniaturizeOnDoubleClick"];
NSNumber *fastUserSwitching = [userDefaults
objectForKey:#"MultipleSessionEnabled"];
NSLog(#"AppleMiniaturizeOnDoubleClick == %#",
([miniaturize boolValue] ? #"YES" : #"NO"));
NSLog(#"MultipleSessionEnabled == %#",
([fastUserSwitching boolValue] ? #"YES" : #"NO"));
Running that code on my system prints the following results:
AppleMiniaturizeOnDoubleClick == YES
MultipleSessionEnabled == YES
This makes sense, since I have both Fast User Switching and Double-click to minimize options enabled. MultipleSessionsEnabled is stored in the local domain at /Library/Preferences/.GlobalPreferences.plist, and AppleMiniaturizeOnDoubleClick is stored in the user domain at ~/Library/Preferences/.GlobalPreferences.plist.
Sample project: NSUserDefaultsFinagler.zip
"Additionally additionally, that's slow. Please don't do this."
Sorry, but that makes no sense (assuming that we've agreed that we're no longer using addSuiteNamed:). User defaults are cached by the application, making calls take in the matter of milliseconds. Wouldn't there be little noticeable difference between asking user defaults for the value for a key that represents a local application value or one that represents a global value?
AFAIK, this is the only "legal" (App-store-compatible) way to achieve the OP's goal. If there's another more efficient means, then please elaborate.

integerForKey always crashes app

Retrieving a value from standardUserDefaults using integerForKey after saving with setInteger:forKey always crashes app.
If I retrieve value using objectForKey and cast value to int, there are no errors and I can display value in a log message fine. However, when I attempt to assign that int to another int or try to perform a math function on it, like adding, it crashes app as well.
How do I retrieve an int that I've saved to standardUserDefaults? I'm developing against SDK 3.0 but have experienced identical issue in 2.2.1. Thanks.
prefs defined as
NSUserDefaults *prefs;
and retrievePrefs always called before savePrefs. App also calls [prefs synchronize] before exiting.
-(void)retrievePrefs {
prefs = [[NSUserDefaults standardUserDefaults] retain];
if (prefs) {
// doesn't crash app, spits out 'page: 1'
NSLog(#"page: %#", [prefs objectForKey:#"pageIndex"]);
// crashes app with no details
NSLog(#"page: %#", [prefs integerForKey:#"pageIndex"]);
}
}
-(void)savePrefs {
if (prefs) {
[prefs setInteger:1 forKey:#"pageIndex"];
}
}
Your second NSLog call is what's causing it: the %# qualifier is used to print out Objective-C objects or CoreFoundation CFTypeRefs, so it's interpreting that integer as a pointer and attempting to dereference it.
What you need is:
-(void)retrievePrefs {
prefs = [[NSUserDefaults standardUserDefaults] retain];
if (prefs) {
NSLog(#"page: %#", [prefs objectForKey:#"pageIndex"]);
NSLog(#"page: %ld", (long)[prefs integerForKey:#"pageIndex"]);
}
}
Check the API documentation for NSLog, printf, and -[NSString stringWithFormat:] for more information.