Objective C - Problem with objectForKey - iphone

Okay, I'm trying to write a high score function for my app.
My problem is that when no high score has been saved yet, my program crashes.
If I save it with:
[[NSUserDefaults standardUserDefaults] setObject:#"[given string]" forKey:#"firstName"];
first, it works fine. However, if I start up the program for the first time and try to view the high scores with the following code:
first = [[NSString alloc] initWithString:[[NSUserDefaults standardUserDefaults] objectForKey:#"firstName"]];
bad things happen.
Basically, is there away to see if nothing yet exist under firstName? Is there a way to initialize without erasing any name that might already be present?
Thanks.

The NSString documentation for initWithString: says
Parameters
aString
The string from which to copy characters. This value must not be nil.
The documentation for objectForKey: says
Return Value
The object associated with the
specified key, or nil if the key was
not found.
The problem seems to be that there is a nil returned when you try to retrieve firstName that doesn't exist yet and try to create a NSString with it as input.

The NSUserDefaults instance method registerDefaults: is meant for exactly this purpose: You can set default values for your preferences that will be overridden by any other value set for the same preference key. Just make sure to call it early enough that it will run before any code that needs to access your preferences.

You could load "first" like this:
first = [[[NSUserDefaults standardUserDefaults] objectForKey:#"firstName"] retain];
if (!first) {
// set default or do something else if there wasn't a value saved
first = #"N/A";
}

Related

Bugg with NSDictonary sometimes crashes with "Trying to insert nil" response

I´m having a great deal of trouble with a bugg that in some cases gives the "initWithObjects:forKeys:count:]: attempt to insert nil object from objects[6]'" error message. It´s only occurring around every third trial and in the other cases it works fine. I haven´t debugged buggs that happeds at certain situations so that´s whyI´m asking for help. It might be difficult to find a solution but I would be glad to receive some advice atleast where to start look for the issue and how to debug it?
Basically my app have a method that sends a NSDictonary list with objects to a server. In some cases I want to remove an object if the user have´t chosen anything for that list. Problem is that the server died´t like sending an object with an empty space. So I made a kind of not nice looking work around. Basically The IBaction checks if the Object contains nil. If it contains nil it redirects to a very similar NSDictonary without the Object containing Nil. It woks in some cases but it seems like it sometimes don´t recognize the object containing nil.
Here´s the code in Inaction that redirects the code. I haven´t created a method for inserting anything to the object:#"DefaultCreateImageID" yet so as far as I know it should always be nil.
NSString *Imagestuff= [[NSUserDefaults standardUserDefaults] objectForKey:#"DefaultcreateImageID"];
if ( [[NSUserDefaults standardUserDefaults] objectForKey:#"DefaultcreateImageID"] != ( NSString *) [NSNull null] ){
DataHandler *handler = [DataHandler sharedDatahandler];
handler.authenticateUserDelegate = self;
[handler createActivityNoImage];
NSLog(Imagestuff);
}
else
{
DataHandler *handler = [DataHandler sharedDatahandler];
handler.authenticateUserDelegate = self;
[handler createActivity];
}
Thanks!
If you get an error "Trying to insert nil" then one of the objects you are trying to add to the dictionary is nil. Solution: Don't do that.
Figure out why some item you are trying to add to the dictionary is nil. Then figure out what you want to do in the situation.
To follow up on gnashers729 answer. The problem for me isn't nil but the fact my Inaction fails to reqognize what the object contains.
Here for example I tried to replace nil with a string to reqognize. I get the same problem that my method in some cases recognize the string and in some cases don´t.
NSString *Imagestuff= [[NSUserDefaults standardUserDefaults] objectForKey:#"DefaultcreateImageID"];
if ( [[NSUserDefaults standardUserDefaults] objectForKey:#"DefaultcreateImageID"] != ( NSString *) #"noImage" ){

Object is deallocated - why? where?

Ok, I spent the last 8 hours fighting with it - it just seems beyond me. Here's my complete (relevant) code:
- (void)updateUserDefaults
{
NSMutableDictionary *viewControllerDetails = [[NSMutableDictionary alloc] initWithCapacity:4];
[viewControllerDetails setObject:[NSNumber numberWithInt:OOVenueClassControllerType] forKey:#"classType"];
[viewControllerDetails setObject:self.searchTerm forKey:#"searchTerm"];
[viewControllerDetails setObject:self.searchLocation forKey:#"searchLocation"];
//----- the next two lines cause the problem
NSString *res = [[NSString stringWithFormat:#"%#",[searchResults xmlString]] retain];
[viewControllerDetails setObject:res forKey:#"searchresults"];
//-----
NSMutableArray *viewControllersList = [NSMutableArray array] ;
[viewControllersList addObject:viewControllerDetails];
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
//the following line causes the error
[defs setObject:viewControllersList forKey:kViewControllersKey];
[defs synchronize];
[res release];
}
Note the block with the next two lines cause the problem. At first I didn't create another string, but added it later while trying to solve the problem.
If I comment out those two lines, everything works fine. If I put them back in, I get
- [CFString class]: message sent to deallocated instance 0xa1a9000
Is something is wrong with the string that I'm trying to put into the userdefaults? That string is rather large (about 200,000 characters), but I had stored even longer strings in user defaults in the past.
It's also worth noting that if I uninstall the app, then everything works fine. But on subsequent runs the problem exhibits itself.
So, how and why and where is the string getting deallocated? I have explicitly added retain - but that still doesn't help. What am I missing?
Edit: just realised I forgot to say that the error is thrown on line
[defs setObject:viewControllersList forKey:kViewControllersKey];
Also, for general information, method - (NSString *)xmlString on searchResults does exactly what the name means: creates an XML string with the information from that object.
Edit 2: I tried doing something else with that string - convert it to NSData, compress using zlib - but regardless of data type, that particular object gets deallocated. Does it have to do something with the size of the string?
NSString *res = [[NSString stringWithFormat:#"%#",[searchResults xmlString]] retain];
Is auto released. You don't need to release it at the end of your method. You are over-releasing it.
Further, you don't need to retain the [searchResults xmlString]. The stringWithFormat method already does it for you.
Good Luck!
Ok, not sure what exactly the problem was, but it was somewhere in the searchResults and/or xmlString method. searchResults object is originally created from XML received from the server (XML is parsed into the object structure). When xmlString was called, for some reason the string I was getting back was different from the original XML (I'm not talking about formatting, of course) - of 200,000 char-long string, within the first 500 chars or so there were some differences. I haven't been able to figure out why. So, instead of recreating the xml from object structure, I instead stored the original XML in a field in that object and, when xmlString was called, simply returned the original string. Now everything worked fine.
Thank you all for your support through this painful process.

How to call a specific user default in iOS

I'm trying to call up a specific setting that is saved in the settings bundle of my iphone app. I want to do an if statement based on what was saved. My code in the implementation file looks like this:
branchMatch = [[NSString alloc] initWithString:[defaults objectForKey:#"branch"]];
The object in the settings file is just the name of the specific branch. I keep getting the SIGABRT error but I'm not sure why.
First, there's no need to initialize a string with another string. Assuming that branchMatch is a NSString*, the following would suffice:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
branchMatch = (NSString*)[defaults objectForKey:#"branch"];
Second, check if objectForKey returns a nil. Your SIGABRT is, most likely, due to a nil there.
EDIT: those values aren't present in the collection until the user opens up Settings and explicitly changes them. It's up to you to provide the sensible value if the setting is not found (is nil). The ones in the settings bundle are not automatically applied; they are only for the Settings app to initialize its UI properly.
Are you checking that the objectForKey call isn't returning nil?
Do this instead :
NSUserDefault* defaults = [NSUserDefauls standardUserDefaults];
NSString* setting = [defaults objectForKey:#"branch"];
if (setting)
// Do what needed if branch is set
else
// Do what needed if branch has never been set
After much hair pulling, I realized that I need to load user defaults from the delegate class instead of the ViewController.
Thanks for all the input

What is the use of -[NSUserDefaults registerDefaults:]?

What is the difference between:
[[NSUserDefaults standardUserDefaults] registerDefaults:
[NSDictionary dictionaryWithObjectAndKey:anObject, #"something"]];
And this:
[[NSUserDefaults standardUserDefaults] setObject:anObject forKey:#"something"];
The difference is that the first code-snippet you register defaults that will be used when the user has not made any changes to the "property".
So if you want to provide let's say a "property" with the key name 'Welcome message', you could instead of having the property returning nil insert a default message 'Welcome first-time user' that will be displayed when there have been no changes to the property.
This will simplify your logic because you don't need to write an if test to check if the "property" returns nil and then make another message if this is the case.
NSString *greeting = [[NSUserDefaults standardUserDefaults] stringForKey:#"Greeting"];
if(greeting == nil) {
NSLog(#"Welcome first-time user!");
}
The second code-snippet you posted is for setting the property to another value. You will have different set methods (setString, setObject, setBoolean) to set values depending on your program state in the Userdefaults.
EDIT-----Updates as requested in comment.
The first method is for registering values to defaults, as the name implies. The first time you access the property with some key name the value will be either nil for objects, false for booleans or 0 for numbers. Instead of doing a lot of tests and so on to so if the values is not set in the program, and then do something "default" action such as the example above, you can ship your application with some already predefined values for these keys.
A typical place to put the registerDefaults is in the initializer-method in the appDelegate.
Then somewhere in your program you may want to set the values of these fields then you use the setObject, setString, setBoolean...and for retrieving you use stringForKey, objectForKey...
Think of it as this
The registerDefaults is the constructor where you may supply sensible values for the object, otherwise you get some defaults which I already wrote. Then later if you want to change the object's attributes you do NOT use the "constructor" but the set/get methods.
Long story short,
[[NSUserDefaults standardUserDefaults] setObject:#"Entropy" forKey:#"kName"]
will save "Entropy" to a file named com.example.Demo.plist in Library/Preference folder (where com.example.Demo is your Bundle ID, see IOS Application Security Part 20 – Local Data Storage)
[[NSUserDefaults standardUserDefaults] setObject:#"Mac OS X" forKey:#"kOS"];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
#"Windows", #"kOS",
#"Google", #"kSearchEngine", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
NSLog(#"%#", [[NSUserDefaults standardUserDefaults] objectForKey:#"kOS"]);
NSLog(#"%#", [[NSUserDefaults standardUserDefaults] objectForKey:#"kSearchEngine"]);
NSLog(#"%#", [[NSUserDefaults standardUserDefaults] objectForKey:#"kBrowser"]);
will print "Mac OS X", "Google", (null)
In fact, registerDefaults
does not save to disk
only sets value for keys that haven't been set ("kOS" is set by setObject:forKey: and "kSearchEngine" is not set)
returns 0 for scalar values, nil for objects if that key is not set by both registerDefaults and setObject:forKey: ("kBrowser" in this case)
And the usage of registerDefaults
Quoted from Preferences and Settings Programming Guide
If these standard default values are not appropriate for your app, you
can register your own default values using the registerDefaults:
method. This method places your custom default values in the
NSRegistrationDomain domain, which causes them to be returned when a
preference is not explicitly set.
Quoted from How to Save Data with NSUserDefaults
Another tip is that you can initialize your NSUserDefaults with a
pre-defined NSDictionary object. So for example you could set a
default value to be “false” or “true” before the user ever had a
chance to interact with your program. In my case, sometimes I create
an array that represents all the levels in my game, and in each array
value I store a boolean to check if a player has finished the level.
To do this I create the data object and then register it with
NSUserDefaults. If a previous value exists for the object, then
nothing happens. Otherwise my blank object gets saved as the “default”
defaults
PS: Ole has a very good article explaining Handling Default Values With NSUserDefaults in detail
Another way of looking at it is this. If you delete the preferences file from ~/Library/Preferences, the defaults that are set by registerDefaults will be the ones that apply to the application until new preferences are set.
In Swift 2.1.1 Xcode 7.2
I added this snippet to application:didFinishLaunchingWithOptions to initialise the tintColorsIndex which is one of parameters user can change in the app.
let defaults = NSUserDefaults.standardUserDefaults()
defaults.registerDefaults([
"tintColorsIndex" : -1,
])
When the app is launched the very first time the tintColorsIndex will be assigned a value of -1 (an Int). If user has changed the color while using the app, their preference won't be overridden at subsequent launches.
User Defaults are grouped in domains...
registerDefaults is used to add defaults to the registration domain..
You can read about the domains in Preferences and Settings Programming Guide.

Unexpected results from NSUserDefaults boolForKey

After uninstalling an application completely from the device and then loading it in the debugger, I am attempting in a setup method to load a flag using boolForKey. The first time the app runs I have the expectation that the bool will not exist, since I have just reinstalled the app. I expect from the documentation that boolForKey will therefore return NO.
I am seeing the opposite though. boolForKey is returning YES, which fubars my initial user settings. Any idea why this might be happening or a good way around it?
BOOL stopAutoLogin = [[NSUserDefaults standardUserDefaults] boolForKey:#"StopAutoLogin"];
_userWantsAutoLogin = !stopAutoLogin;
So stopAutoLogin comes out as "YES", which is completely unexpected.
Stranger and stranger: When I call objectForKey:#"StopAutoLogin" I get a nil object, as expected. It's just the boolForKey that returns a bad value. So I changed the code to this:
// this is nil
NSObject *wrapper = [[NSUserDefaults standardUserDefaults] objectForKey:#"StopAutoLogin"];
// this is YES
BOOL stopAutoLogin = [[NSUserDefaults standardUserDefaults] boolForKey:#"StopAutoLogin"];
please try [UserDefaults synchronize];
Because this method is automatically invoked at periodic intervals, use this method only if you cannot wait for the automatic synchronization (for example, if your application is about to exit) or if you want to update the user defaults to what is on disk even though you have not made any changes.
please see: http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
Do you register the default values for your keys?
NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithCapacity:1];
[appDefaults setObject:#"NO" forKey:kReloadOnStartKey];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults registerDefaults:appDefaults];
If there is no registration domain,
one is created using the specified
dictionary, and NSRegistrationDomain
is added to the end of the search
list.
The contents of the registration
domain are not written to disk; you
need to call this method each time
your application starts. You can place
a plist file in the application's
Resources directory and call
registerDefaults: with the contents
that you read in from that file.
See this link for more information.