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.
Related
Just wondering if anybody experience this issue?
I am developing an application in iOS using Objective-C at the moment.
Sometimes my data in NSUserDefaults will be missing after I compile the app.
But if I ignore it and recompile the app again the data suddenly reappears.
I already synchronized in several places (not in every key, but only in several places).
If anyone happened to face this issue before I hope you can share how to handle this issue.
P.S. I need a storage to save 1 particular object so I can retrieve it when the app reopens.
Edited to add the code
NSString *enPIN = [[NSString alloc]initWithString:[NSString stringWithFormat:#"%#", [enterField.text md5]]];
[[NSUserDefaults standardUserDefaults] setObject:enPIN forKey:#"pin"];
[[NSUserDefaults standardUserDefaults]synchronize];
NSLog(#"check pin %#", [[NSUserDefaults standardUserDefaults] objectForKey:#"pin"]);
The object is a string, i hash it using md5 and then store it in nsuserdefault, if it only randomly dissapearing maybe its not weird, but its also reapearing again after it dissapear if i recompile the apps
Check if your defaults are using only string value or bool or such.
If you are using any Object with Class (key and parameters) like NSObject to store in defaults I prefer you do the encoding and decoding accordingly before storing and retrieving the values.
Also If you storing any NSDictionary check if any of the object value inside that dictionary is not anything other than Bool, String , if there also any NSObject class or reference is stored then you may face same issue.
Refer this stackoverflow link as to how encode objects before storing to NSUserDefaults.
Lastly [defaults synchronise] call mandatory on viewwilldisappear or immediately after storing new value whichever way is your implementation.
Hope this helps.
I had a similar issue the other day with NSUserDefaults
Not quite sure what was causing it, but it was due to a bug in Xcode. I was able to fix the issue without changing my code at all. I simply cleaned the project (CMD-Shift-K) and restarted my computer, and then it worked just fine. It's worth a try
Are you getting any kind of error messages in the console?
I have this NSUserDefaults setup code
NSDictionary *appDefaults = [NSDictionary
dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:#"DidBuyInAppPurchase"];
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
I check later like so:
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"DidBuyInAppPurchase"] == YES){//some code}
Does this mean that a BOOL exists for the key or that the key was set to YES?
The odd behavior was that I wrote code in viewDidLoad that set the BOOL to YES just to test. Prior to doing this, the if-statement did not proceed. Now it does even when I remove the code that sets standardUserDefaults to YES is removed. Even if I remove the app and build and run on the device, the code proceeds.
This would indicate that the if check compares the YES to whether a boolean value exists there or not, and somehow the YES is persisting on app removal as well. However, the initial run of the app should have caught this too since a value existed. I checked this SO question and it seems that the if check should check for a YES value and not existence of a value.
This is for an IAP - when user purchases, the boolean is set to YES to show that it has been purchased, and if so, different viewControllers are presented to the user. How can I set this up properly so that setting to YES performs properly in if checks?
EDIT: Seems like this was in issue in Simulator on iOS 8 - the default not deleting from the Application domain. Not sure if this is the case here on the device in iOS 9 or I'm just using NSUserDefaults incorrectly.
When you use the -boolForKey: method, it internally does 2 things. First, it looks up an object for the key and second, it tries to unwrap a BOOL from an NSNumber object (returning that BOOL).
If that NSNumber is not present, the return will be "NO". If that NSNumber is present, the boolean the object is wrapping will be returned.
To check for the reason NSUserDefaults returned NO, you may call -objectForKey: with the same key and simply test the existence of an object.
Apple states, that the method you use to import predefined settings won't save anything, it just adds the dictionary's entries to the in-memory defaults. I have not seen your whole code on my small display... -registerDefaults:
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.
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.
Is it ok to read a value stored in NSUserDefaults from multiple views or you only read it once and then you use another method to pass the data to different part of your app?
In other words I want to know if I’m doing it right, what I’m currently doing in an app I’m working on is basically saving a couple of NSIntegers and NSStrings (only two or three of each) in NSUserDefaults and then I’m reading those values in different parts of my app (different views) but I was wondering if this is a common practice or should I be doing something different like, read the value somewhere in the app and then try to use a different method to pass that data to other views. I want to learn best programming practices, that’s all.
What is the most common practice when using NSUserDefaults values in multiple parts of your app?
FYI,
I’m familiar with multiple ways to pass data between view controllers such as, delegation, prepareForSegue etc.
Thanks a lot.
I would also recommend, to read it multiple times and do not introduce another layer to hold the data.
The most important aspect is imo the actuality of the data, which might be changed inbetween different invocations.
It is ok to read and even write values to NSUserDefault in multiple places, but it is a better practice to have a global mechanism (like a singleton pattern) to read and write to UserDefaults. this way you'll be guaranteed to have fully synchronized values. all you need to do is to create a new class and add a few Class methods to read and write values from NSUserDefaults.
It is OK, when you call [NSUserDefaults standardUserDefaults] it will return the same object whether you spread the calls everywhere in the app or you encapsulate the access in a class.
I prefer the later as it allows you to have more readable code (and other benefits):
BOOL hasX = [TLPSettings hasPreferenceX];
if (hasX) {
[TLPSettings setY:YES];
}
vs
BOOL hasX = [[NSUserDefaults standardUserDefaults] boolForKey:#"hasX"];
if (hasX) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"hasY"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
You can change the preferences keys easily (without defining consts for them), change the validation or logic of a preference without messing with it everywhere, debug its usage or rogue values easily, store all or part of them in a different place, etc.
TLP is your three letter prefix.