integerForKey always crashes app - objective-c

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.

Related

NSUserDefaults not writing to disk

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).

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.

Calling Obj-C Code from JavaScript via Console: Arguments get dropped?

Having a heck of a time with this one.
I've got a super-simple Cocoa app containing one WebView, a WebScripting API defined in the page, and a single NSObject defined on that API. When I turn on the debugger tools (in the embedded WebView), I can see the API on the JavaScript window object, and I can see my "api" property defined on that -- but when I call the API's "get" method, the arguments aren't being serialized -- when the Obj-C method gets called, the arguments are missing. See below, which hopefully illustrates:
I've combed through the docs, I've (apparently) set the appropriate methods to expose everything that needs to be exposed, and I can see the method being called. There has to be something stupid I'm missing, but as a relative newbie to this environment, I'm not seeing it.
Thanks in advance for your help!
Have you set WebKitDeveloperExtras to YES in your default user defaults when you send -[NSUserDefaults registerDefaults:]?
Depending on what version of Xcode you're using you could be getting a known error. If you're using LLDB on anything but the most recent version, it might not be giving you the right variables in the debugger. The solution has been to use GDB instead of LLDB until Apple fixes the problem. But I think they fixed the problem in the latest version. I'd change the debugger to use GDB and see if you're getting the right variables in Xcode. (Product-> Edit Scheme...-> Run -> Debugger). I came across this problem in iOS, though, so I don't know its applicability to OSX. Worth a try anyway.
I originally came across the problem here: https://stackoverflow.com/a/9485349/1147934
I process javascript in the main thread of my app from a local file stored in the apps directory. I check for beginning and ending tokens for the js functions I am executing and whether the function contains a variable.
Hopefully this can give you some good ideas for your issue. You could also do alerts in the js to see if the values post correctly as you run the app (I am sure you thought of that already, but it's worth mentioning.) Happy coding! I hope this helps!
in the .h file define:
NSMutableString *processedCommand;
NSArray *commandArguments;
In the .m file:
// tokens
#define kOpenToken #"<%%"
#define kCloseToken #"%%>"
// this will throw
-(void)executeJScriptCommand:(NSString *)aCommand {
[self performSelectorOnMainThread:#selector(executeThisCommand:) withObject:aCommand waitUntilDone:YES];
}
// this will throw
-(NSString *)executeCommand:(NSString *)command {
NSString *aCommand = [[[command stringByReplacingOccurrencesOfString:kOpenToken withString:#""]
stringByReplacingOccurrencesOfString:kCloseToken withString:#""]
stringByTrimmingLeadingAndTrailingWhitespaces];
if ([aCommand hasPrefix:#"="])
{
// variable. get value
[self getVariableFromCommand:aCommand];
}
else {
[self executeThisCommand:aCommand];
}
NSString *returnValue = [NSString stringWithString:processedCommand];
self.processedCommand = nil;
self.commandArguments = nil;
return returnValue;
}
-(void)executeThisCommand:(NSString *)aCommand {
BOOL hasError = NO;
// clear result
self.processedCommand = nil;
self.commandArguments = nil;
BOOL isFromJS = NO;
NSString *function = nil;
NSMutableArray *commandParts = nil;
#try {
// first, break the command into its parts and extract the function that needs to be called, and the (optional) arguments
commandParts = [[NSMutableArray alloc] initWithArray:[aCommand componentsSeparatedByString:#":"]];
if ([[[commandParts objectAtIndex:0] lowercaseString] isEqualToString:#"js-call"]) {
isFromJS = YES;
[commandParts removeObjectAtIndex:0];
}
// get our function, arguments
function = [[commandParts objectAtIndex:0] retain];
[commandParts removeObjectAtIndex:0];
if ([commandParts count] > 0){
if (isFromJS == YES) {
NSString *arguments = [[commandParts objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
if ([arguments length] > 0) {
self.commandArguments = [arguments JSONValue];
}
}
else {
self.commandArguments = [NSArray arrayWithArray:commandParts];
}
}
// build invoke
SEL sel = NSSelectorFromString(function);
if ([self respondsToSelector:sel]) {
[self performSelectorOnMainThread:sel withObject:nil waitUntilDone:YES];
// using invocation causes a SIGABORT because the try/catch block was not catching the exception.
// using perform selector fixed the problem (i.e., the try/catch block now correctly catches the exception, as expected)
}
else {
[appDelegate buildNewExceptionWithName:#"" andMessage:[NSString stringWithFormat:#"Object does not respond to selector %#", function]];
}
}
#catch (NSException * e) {
hasError = YES;
[self updateErrorMessage:[NSString stringWithFormat:#"Error processing command %#: %#", aCommand, [e reason]]];
}
#finally {
[function release];
[commandParts release];
}
if (hasError == YES) {
[appDelegate buildNewExceptionWithName:#"executeThisCommand" andMessage:self.errorMessage];
}
}
// this can return nil
-(NSString *)getQueryStringValue:(NSString *)name {
NSString *returnValue = nil;
if (queryString != nil) {
returnValue = [queryString objectForKey:[name lowercaseString]];
}
return returnValue;
}

Empty value in NSUserDefaults

I am developing a cocoa application. I created a user default object with this code:
NSUserDefaults *standard_user_defaults = [NSUserDefaults standardUserDefaults];
if (standard_user_defaults) {
[standard_user_defaults setObject:myString forKey:key];
[standard_user_defaults synchronize];
}
And then, I am getting the value with this code:
NSUserDefaults *standard_user_defaults = [NSUserDefaults standardUserDefaults];
NSString *val = nil;
if (standard_user_defaults)
val = [standard_user_defaults objectForKey:key];
The problem is that if I run this application in my computer is working fine, and I can get the user default value, but if another person runs the application is getting an empty value. Any ideas?
I assume by “another person” you mean another user on your computer. The reason they cannot read what you’ve written to your user defaults is because NSUserDefaults is meant for user-specific values. To save something that your application will be able to access from both your user account and others’, you need to place it in a file stored somewhere in a shared location on the system. One way to do this would be something like this:
NSString *basePath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, YES) objectAtIndex:0];
NSString *filePath = [basePath stringByAppendingPathComponent:#"someFileName"];
// write:
NSDictionary *dataToSave = [NSDictionary dictionaryWithObjectsAndKeys:theValue, #"someKey", nil];
if(filePath && [dataToSave writeToFile:filePath atomically:YES])
{
// success!
}
// read:
NSDictionary *dataToLoad = [NSDictionary dictionaryWithContentsOfFile:filePath];
if(dataToLoad)
{
// success!
NSString *theValue = [dataToLoad objectForKey:#"someKey"];
}
You should try and use valueForKey rather than objectForKey. Looking at your code, there is no reason it shouldn't work for one person and not another.
The one exception would be if you are using the defaults from the settings.bundle. Values stored into settings.bundle for user defaults are not initialized until the user opens the settings page for the first time.