NSUserDefaults sometimes returns wrong value - ios

I save the user profile picture ID of a user as a string in NSUserDefaults e.g. #"12". When I do that, I call the synchronize method immediately.
When I read this value from NSUserDefaults, it returns #"12" in maybe 99% of the time. But sometimes, it returns a different value (which I cannot find due to the rarity of the event, but suspect it is either nil or some default value (?)).
The code I use to write/read is very simple:
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:#"12" forKey:#"photoID"];
[userDefaults synchronize];
NSString* photoID=[userDefaults objectForKey:#"photoID"];
I know the value sometimes returned is incorrect because the app at the time behaves as if the value was different (i.e. user contacts are notified the profile picture has changed).
And when that happens, the next call to objectForKey returns the correct value, so user contacts received another notification the profile picture has changed again.

NSSynchronize is not guaranteed to succeed and returns a BOOL.
Note that syncrhonize simply writes the data to disk but NSUserDefaults keeps the data in memory as well. Calling it after every write is probably not needed, though I've done it myself and many examples on the net do that.
From the Apple documentation:
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.
Disclaimer: speculation
It's possible that the act of failed synchronize is causing objectForKey to fail. The class knows the data it has may be wrong and so returns nil instead. This is more likely if you are calling synchronize successively and excessively or otherwise doing IO intensive operations.

Related

Is it possible that UserDefaults.standard values will not be available to be read?

I know that UserDefaults are meant simply to save preferences, but these preferences are persistent - the values saved in UserDefaults are maintained over an unlimited number of app launches and are always available to be read as long as the app remains installed... right?
Is it possible that these values will be cleared or will not be correctly accessed at any point? After years of using UserDefaults and depending on the consistency of the values they hold, I have now seen twice in one day of work that when my app launched and checked a simple boolean value, the value was not correct.
if defaults.bool(forKey: "beenLaunched") {
This code runs each time the app launches. If the value is true, I do nothing more, but if it is false, I set a few values as this is the user's very first launch of the app and then I call defaults.set(true, forKey: "beenLaunched") and defaults.set(0, forKey: "eventsCompleted") and a few other values.
I found this thread on the Apple forums in which Eskimo said "For the central NSUserDefaults method, -objectForKey:, a result of nil means that the value is unavailable, but there’s no way to distinguish between this key is not present and this value can’t be fetched because the user defaults are offline." (This appears to be in reference to a specific case of background launching while a device is locked)
I can look into a more secure way of saving simple data such as a Bool value, an Int, or a String, but using UserDefaults for these types of values has always been simple, straightforward, and reliable. Can anybody chime in on the matter and if I was wrong to believe in UserDefaults' persistence?
Thank you!
UserDefaults isn't a "service"; it's never not available to your application. The file it writes to is a PLIST (and therefore all values are stored according to the PLIST standard). For example, all numbers (including booleans) are stored as an NSNumber to the file and can be retrieved either by object(forKey:) or bool(forKey:). If you use the object method and nothing is set for that value you get nil, whose boolean value is false (or 0). Same if you use the boolean method (you get false). This means no matter which way you go, you'll always get false if there's no value or a value of false. Design your logic around that (which you already have - "beenLaunched" will be empty and therefore false if it's never been launched) and you should be fine.
As for the suggestion of synchronize(), ignore it. Unless you're doing something really weird with threads and preference access or you've interrupted the application immediately after setting a value/object for the problem key, it's got nothing to do with this. Per the very first paragraph of the docs, synchronize() is called periodically as needed. In practice, it's called pretty much immediately after a change occurs.
For context, none of my apps have ever called synchronize() and some of them are old enough to drive. Never a single problem. If you don't have a very good justification for calling synchronize() yourself you almost certainly don't need it and attempts to explain why you do need to sprinkle it everywhere are ... often amusing.
In your specific case, the value stuck by after first run multiple times then suddenly didn't once. Is it possible you changed your app's bundle identifier or name? The defaults are stored by identifier+name so a change would effectively "reset" your app's defaults. Have you been running your app in the simulator and did you just reset-content-and-settings in the simulator? On your device and deleted the app before re-running it on-device?
If you are working in swift then returning nil means objectforkey has not been assigned any value at all . In other case it always returns proper value if you casted saved value properly.
And userdefaults is always available to use, it can never goes offline.

IOS - Storing value in a Static Variable like php Sessions

I have an app that needs to keep hold of how many people have joined a room, my problem is keeping a hold of this value. I need to increment this value by one every time the user joins. So i need to check that the room is not full.
I have been thinking of using sessions and keeping track of this value in php, but is there another way natively, so my question is how do i retain or persist this value?
I tried this running on two phones but the value is always 1 when the global value (myData) should be 2 when the second phone runs the app:
myData = myData + 1;
[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithFormat:#"%d", myData] forKey:#"myData"];
NSLog(#"%d",myData);
If you want to synchronise information between the devices in your case, you should update the data using your server (or how you handle the networking between the devices and rooms etc.)
For example if you install the app in a new phone, it should firstly fetch the value from your server and save the NSUserDefaults so that it can be synchronised.
However, with this way, I don't think you need to store data in the phone, so you can directly use information from server to check the room fullness etc. Storing as NSUserDefaults looked unnecessary to me.
I hope I got the question and your app right.

Using NSUserDefaults to store unique order ID

I'm developing a Point of Sale-kind-of-app, which runs on iOS and stores orders and payments in MySQL. I designed the order-table so that there is a client ID and a ClientOrderID which is a combined unique index, preventing duplicate sales.
The iPad gets its Client ID the first time it connects to the server and validates. This means no two iPads will ever get the same Client ID. If they reconnect they get a new one which is from a an auto-increment value of a column in another table designed for this.
Now, the client must of course also have a ClientOrderID to deliver to the server, so that the unique index becomes useful. What I did here is create a static method that looks like:
+(int)getNewOrderOrSaleID {
int orderid = [[NSUserDefaults standardUserDefaults] integerForKey:#"orderid"];
[[NSUserDefaults standardUserDefaults] setInteger:orderid+1 forKey:#"orderid"];
[[NSUserDefaults standardUserDefaults] synchronize];
return orderid;
}
My questions is now this: Is this a reliable method, or does NSUserDefaults tend to mess things up?
And this is probably a bit far out, but doing this every time someone makes an order from the iPad, would that cause wear on the internal storage over time? This question is close to rhetorical, as I do realize how small the data amount is.
As it's only possible to make one order at a time, this method will never run two times at once.
Since you are overwriting the same thing, you won't have new records in the user defaults, the existing one will change. And if you want to persist only some small information (like this int) the user defaults is a good place to do it. However, as Wain mentioned in the comment, the counter will be reset when you reinstall the app.

NSUserDefaults standardUserDefaults objects are being removed randomly

I am facing a very weird behavior for NSUserDefaults, the problem is that [NSUserDefaults standardUserDefaults] objects are being removed randomly!
My [NSUserDefaults standardUserDefaults] contains around 65 objects(60 small NSStrings and 3 arrays which the maximum count could be 4 and 2 other arrays with maximum count 30..notice that it has never been the maximum case when facing this problem) , one of these objects is a value checking if the user has already completed the registration phase.
When launching the application, sometimes this NSUserDefaults will contain only 5 objects from those 65 and the others are being removed from the plist without appearing again even if i relaunch the app., which lead the user to the registration phase again!!
i am pretty sure that i am using the save function correctly
[[NSUserDefaults standardUserDefaults] setObject:#"Value" forKey:#"Key"];
[[NSUserDefaults standardUserDefaults] synchronize];
i have searched google for similar behavior without finding anything that can help!
Does anyone faced such behavior and what is the solution to fix it?
Thank you for any help
I do want to help out here, since I have had exact same strange behavior with one of my projects.
So bear with me, what happened to my project is that: I had a singleton class, which encapsulates several properties, and I had overridden setters and getters for those properties. In a setter method, I get standardUserDefaults instance and set object for key, and synchronize. In a getter method, I return the object of the key. Also I have a login success indicator value to indicate if login is successful. And same as your issue, my objects disappear. After few days of struggle, it turns out that the login indicator got initialized to false when Network became unreachable. And in the indicator false clause, I was setting nil objects to user defaults.
My points are:
Double check if standardUserDefaults setObject:forKey: function calls are logically correct, make sure the objects are NOT nil when call
Check if you have logic (login, Network change etc) that invalidates the objects.
Run tests, find the minimal steps to replicate the issue. Use Debug session to examine the objects. (Give up believing in Random Disappearing)
Hope this gives a lead.

Is it bad to read or write to NSUserDefaults in a tight loop?

Are there any performance or other consequences for setting an object for key in the NSUserDefaults repeatedly?
[self.myDefaults setObject:someObject forKey:someKey];
and/or
anObject = [self.myDefaults objectForKey:someKey];
My loop will repeat approx 100 times within a second, and only last for a few seconds at a time.
I'll only be calling synchronize after the loop is finished.
The NSUserDefaults docs state:
"NSUserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value."
Therefore it's no different to setting or getting an object from an in-memory dictionary variable.
However, calling [self.myDefault synchronize]; within a type loop would have a performance consequence, as this opens and writes to the defaults database on disk.
From my understanding, NSUserDefaults isn't the best data store for mass amounts of data. It's recommended to write to plists, or sqlite for larger data sets. User defaults was just for a few user preference settings and isn't built for "storing data" like a typical database is.
You shouldn't really run into performance issues in the loop, though, unless if you're synchronizing for each iteration.

Resources