NSUserDefaults objectForKey sometimes nil - ios

I've just noticed that a key that exists in my **NSUserDefaults**
is returning nil quite often. It seems about half the time it's
correct and the other half it's not. I say half the time, but I dont
mean that its flip-flopping, its just 40-50% I see it not work.
When I write the initial value, I call synchronize immediately.
I use this key as the applications
revision I set when a new user signs up.
The following code returns nil:
#define kDBrevision #"revision"
NSString *rev = [[NSUserDefaults standardUserDefaults] objectForKey:kDBrevision];
When I launch the app and just monitor the value (without
writing any NSUserDefaults), the value sometimes is valid
with no modifications to the NSUserDefaults at all.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *r = [[NSUserDefaults standardUserDefaults] objectForKey:kDBrevision];
NSLog(#"revision %#", r);
_exit(1);
I have no idea why this is happening. Im running iOS 10 on my
device connected to Xcode 8.2.1. Anyone have any ideas?
Thanks
EDIT:
I started talking to apple about fixing this and found out that if you have
file protection complete turned on, it can be the cause of this issue
showing up from time to time, however apple told me that my particular
case (which is the only one I was sure of at this time) is a bug.
The case is when you use Xcode to launch the app on the device it should
not fail like this and it occasionally does. No idea when or if it will
be fixed. Instead I moved my critical strings from the defaults to
the keychain instead.

This appears to be an Xcode 8 and/or iOS 10 bug. I ran into it myself and conclusively narrowed the case to UserDefaults intermittently returning nil when in fact there was data for the key. Specifically, I set the data once, then removed the set logic and executed only the get logic, repeatedly, and sometimes got values, sometimes got nil.
I changed my Run scheme to use Release configuration and ran the app on device without the debugger attached and the issue disappeared, that is, the get logic produced the correct value every time I executed it (~30 times).
Here is some more discussion:
iOS 10 with XCode 8 GM caused NSUserDefaults to intermittently not work
iOS 10, NSUserDefaults Does Not Work
https://forums.developer.apple.com/thread/48700
https://forums.developer.apple.com/message/143155#143155

try below code in didFinishLaunchingWithOptions
Swift 3.0
UserDefaults.standard.synchronize()
Obective - C
[[NSUserDefaults standardUserDefaults] synchronize];

Related

iOS 10 with XCode 8 GM caused NSUserDefaults to intermittently not work

NOTE: I have seen many other posts on Stack Overflow about NSUserDefaults being renamed to UserDefaults in Swift or not working on simulator until a restart. This is not a duplicate by anyway. Many of the questions SO is tagging against is from 4 years ago. My question is specific to iOS 10 from this year as this has always worked in older versions. I have mentioned in my question already that my question is not a duplicate of those questions as those were simulator bugs in swift and my issue is on device objective C bug. Please read the questions before marking as duplicate
My issue is different as I am able to reproduce this on objective C and on physical device itself.
I created a brand new project from scratch for this test. I placed this code in the viewDidLoad of a view controller:
if (![[NSUserDefaults standardUserDefaults] valueForKey:#"checkIfInitialized"]){
NSLog(#"setting checkIfInitialized as not exist");
[[NSUserDefaults standardUserDefaults] setValue:#"test" forKey:#"checkIfInitialized"];
[[NSUserDefaults standardUserDefaults] synchronize];
self.view.backgroundColor=[UIColor redColor];
self.mylabel.text=#"NSUserDefaults was NOT there, try running again";
} else {
NSLog(#"checkIfInitialized exists already");
self.view.backgroundColor=[UIColor blueColor];
self.mylabel.text=#"NSUserDefaults was already there this time, try running again";
}
Now if I run the app about 10 times, few times it finds the checkIfInitialized and sometimes it doesn't. No exact number on how many times it fails as it might work 3 times then fail next 2 times then work 4 times and fail once and so on.
Now something I have noticed (not 100% sure though) that the issue only seems to happen when I am testing connected via Xcode. If I run by launching the app by clicking the app icon on device without Xcode, then it seems to work fine but I can't be 100% sure.
I noticed this error occur sometimes:
[User Defaults] Failed to write value for key checkIfInitialized in CFPrefsPlistSource<0x1700f7200> (Domain: com.xxxx.appname, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null)): Path not accessible, switching to read-only
I have this very simple project on my dropbox if you want to test it out.
I would suggest testing about 10-15 times to reproduce this issue.
https://www.dropbox.com/s/j7vbgl6e15s57ix/nsuserdefaultbug.zip?dl=0
This works completely fine on iOS 9 so definitely something to do with iOS 10.
EDIT
Bug logged: 28287988
Response from apple DTS team:
First off, you should first determine whether standardUserDefaults or
valueForKey is failing. My guess is that “standardUserDefaults” is
returning NULL and, if that’s the case, then that’s something you
should be guarding against generally. Notably, standardUserDefaults
will return NULL if the preference file is encrypted in the
environment the app is currently running in (for example, preferences
is set to “NSFileProtectionComplete” and the app is running in the
background). That shouldn’t be an issue for standard foreground-only
apps, but it’s something to be aware of anyway.
It’s very likely that Xcode is actually inducing the problem here.
Xcode vastly complicates the app launching environment in a way that’s
VERY different than a standard app launch. My guess is that this is
basically being triggered by Xcode’s timing inducing an an expected
situation during the app launch, but if you want a more formal test of
that try setting a single breakpoint in applicationDidFinishLaunching
and continuing in the debugger as soon as you hit it. My guess is
just adding that disrupts the timing enough to stop the problem from
happening. Sort of. It’s iOS 10 only in the sense that iOS 9 will
never print that log message, but that’s because the log message was
added in iOS 10. The code itself is similar enough to iOS 9.3 that I
suspect exactly the same behavior is (at least in theory) possible in
iOS 9.
Yes, this is definitely a reproducible bug.
It happens with the GM release of Xcode 8 and iOS 10.
It is not the linked question referring to Swift.
It is not the linked question referring to beta versions of the Simulator.
The bug happens on devices and on the Simulator. It is intermittent: saving will work six times and then fail. Unlike you, I did not get the "failed to write key" message.
The bug also occurs when operating directly on the device without Xcode. This is in fact how I discovered it.
You should report a bug to Apple, especially since you have a short program that will reproduce it. I will do the same.
One key difference: In my case the failure is in writing the default. The previously written value remains in NSUserDefaults. Sometimes one key is successfully written while another is unchanged.
A similarly very intelligent DTS response from my own support request. Basically, killing using Xcode is more murderous than anything that would naturally happen on the device (even the double-Home-click-and-upswipe method) and since everything is abruptly crashed when Xcode halts it, the lazy writing of NSUserDefaults can fail, or be only half completed.
And indeed, pure on-device testing of the app, without Xcode involved, shows that everything does get correctly written to NSUserDefaults when the app is terminated.
I have closed my own bug report.

Failed to read values in CFPrefsPlistSource iOS 10

I've updated my Xcode 8 to beta 2 today and I'm trying to share data between App and Today Extension. I'm facing with this log warning:
2016-07-08 18:00:24.732472 ProjetctX[941:42801] [User Defaults] Failed
to read values in CFPrefsPlistSource<0x1700f1280> (Domain:
group.x.p.t.o, User: kCFPreferencesAnyUser, ByHost: Yes, Container:
(null)): Using kCFPreferencesAnyUser with a container is only allowed
for System Containers, detaching from cfprefsd
Anyone can help me?
This is actually a spurious warning that was introduced in iOS 10 and macOS 10.12:
NSUserDefaults tip: in the current OSs there's a logged error "…with a container is only allowed for System Containers…".
This is spurious.
Trying to catch a particular failure mode, caught a normal operation case at the same time.
My successor on UserDefaults also has not figured out a way to make this less alarming without making the symptomatic case impossible to debug :/
https://twitter.com/Catfish_Man/status/784460565972332544 [thread]
The advice of prepending your team ID will silence the warning, but will also create a new empty user defaults. This will result in any previously stored data being unreadable.
For the time being, the solution is just to ignore it.
Also, Apple staff member CFM on the forums:
The logged message is spurious unless you're doing very specific things that I don't think are possible without using private functions (it was added to catch misuse of those functions, but unfortunately also caught a normal usage case).
Here’s how to use UserDefaults with App Groups to pass data between your main app and your extension:
In your main app, select your project in the Project Navigator.
Select your main app target and choose the Capabilities tab.
Toggle the App Groups switch to ON. This will communicate with the
Developer Portal in order to generate a set of entitlements.
Create a new container. According to Apple, your container ID must
start with "group", so a name such as "group.io.intrepid.myapp" is
perfect.
Select your extension target and repeat the process of enabling App
Groups. Do not create a new App Group, simply select the group that
was just created in the main app target.
When reading or writing UserDefaults in either your app or your
extension, do not access UserDefaults.standard.
Instead use UserDefaults(suiteName: "group.io.intrepid.myapp").
Note: The suite name is the name of your App Group container created
in Step 4.
Make sure, group enable and using same group id for both extension and app capability section!
Credit goes to http://blog.intrepid.io/ios-app-extensions
Change you group name in Xcode entitlements from:
group.com.mycompany.myapp
To
group.MYTEAMID.com.mycompany.myapp
ps: you can find your MYTEAMID in developer.apple.com membership
The solution for me was to not use the same identifier for the application Bundle Identifier and the part after "group.".
Say, the app bundle id is "com.app.id", then group id as "group.com.app.id" is causing issues. After I change it to "group.com.app.id.something" it stops.
Also had same issue with my macOS app.
Solved it by: Reboot the device!
https://stackoverflow.com/a/39876271
The SuiteName (postfix) must not be the main Bundle ID.
I’m facing this same issue when I’m trying to use initWithSuiteName. Seems that this is a bug from Apple.
The only solution / workaround I found is to reset all the settings of the device.
Go to Settings -> General -> Reset -> Reset All Settings.
This doesn’t erase any content on the iPhone, just erases all the settings.
After resetting the setting, everything worked fine. Let me know if it helps you too.
Build with Xcode 8.1 Beta and you will see the same warning, but you will also get the value.
By default, if you are using the Settings.Bundle/Root.plist for displaying and editing your app preferences via Apple Settings App, it uses the UserDefaults.standard dictionary.
So if you are using App-Groups and you want to share this defaults / settings within your apps and extension, you need to change the container of your settings.
Step 1: Open your Settings.Bundle -> Root.plist
Step2: Add the key ApplicationGroupContainerIdentifier and as value set your App-Group-Id, defined in your Signing & Capabilities: Looks like group.xx.yy
After you have implemented this step, the default container for your App-Settings will now switch from UserDefaults.standard (your apps Path) to the Shared Path.
Set by example
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.xxx.xxx"];
[userDefaults setValue:#"value" forKey:#"key"]
[userDefaults synchronize]; // return maybe false, but it doesn't matter
Get by
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
[userDefaults addSuiteNamed:#"group.com.xxx.xxx"];
NSString *value = [useDefaults valueForKey:#"key"];
Although the same error will still be printed when setting, the value is indeed set and can be read correctly. But I don't know why this is happening, it's just the result of various attempts.
if you suffer this problem when you try to save data to extension APP by using userDefault, maybe you had written this code:
[[NSUserDefaults standardUserDefaults] initWithSuiteName:#"group.xxx.com"];
This code reset default userDefault.
Actually,the correct code is:
[[NSUserDefaults alloc] initWithSuiteName:#"group.xxx.com"];
http://www.jianshu.com/p/e782104c3bc3
Change from
[[NSUserDefaults alloc] initWithSuiteName:#"group.com.xxx.xxx"];
to
[[NSUserDefaults alloc] initWithSuiteName:#"nnnnnnnnnn.group.com.xxx.xxx"];
Where nnnnnnnnn is your team number, the one which you use to sign your code.
Tested under Xcode 8 GM and iOS 10 GM, and worked!

iOS Keychain writing value results in error code -34018

I have an iOS application that stores some sensitive information in the keychain.
While writing values into the keychain, I am getting error code -34018.
I am currently using Apple's iOS KeyChainItemWrapper class.
Both of the following lines of code receive the same error code.
OSStatus res1 = SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes);
OSStatus res = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
This issue does not occur every time, but intermittently. Once I get this error, I am no longer able to write any values to the keychain.
I have printed the error description like so:
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:res userInfo:nil];
and this is what the error prints out:
Error: Error Domain=NSOSStatusErrorDomain Code=-34018 "The operation couldn’t be completed. (OSStatus error -34018.)"
Seems like this is a bug in Keychain, which only appears when you launch your app from xcode. See here: https://github.com/soffes/sskeychain/issues/52
We debugged it a lot and it seems an issue accessing the keychain when
the app is launched from the background. This is only happening with
the debugger (i.e. when launched from Xcode). We think the issue
might be related in our case to the debugger keeping alive the app
even if it should be killed by the OS. We tried in fact to run the
app and then put it in background and launch many other app to occupy
RAM. With the debugger the bug came up when resuming the app from the
background, while without the debugger it didn't (we did run at least
10 tests each).
If someone will come here back with this error and XCode8 with iOS10, probably you have to enable KeyChain Share in the tab Capabilities:
As others have mentioned, this is a Keychain bug, one that Apple is aware of and has been aware of since at least mid-2015.
As of March 22, 2016, however, Apple has said:
We believe these problems were resolved in iOS 9.3.
iOS 9.3 was released on March 21, 2016.
See the thread: https://forums.developer.apple.com/thread/4743
To quote the response by an Apple employee:
Mar 22, 2016 3:28 AM
OK, here’s the latest. This is a complex problem with multiple possible causes:
Some instances of the problem are caused by incorrect app signing. You can easily distinguish this case because the problem is 100% reproducible.
Some instances of the problem are caused by a bug in how iOS supports app development (r. 23,991,853). Debugging this was complicated by the fact that another bug in the OS (r. 23,770,418) masked its effect, meaning the problem only cropped up when the device was under memory pressure.
We believe these problems were resolved in iOS 9.3.
We suspect that there may be yet more causes of this problem.
So, if you see this problem on a user device (one that hasn’t been talked to by Xcode) that’s running iOS 9.3 or later, please do file a bug report about it. Try to include the device system log in your bug report (I realise that can be tricky when dealing with customer devices; one option is to ask the customer to install Apple Configurator, which lets them view the system log). And if you do file a bug, please post your bug number, just for the record.
On behalf of Apple I’d like to thank everyone for their efforts in helping to track down this rather horrid issue.
Share and Enjoy
This held me up for 2 hours before I found a quick "fix" - reboot the iOS device
A quote from the discussion at http://forums.developer.apple.com/thread/4743,
From user littledetails
As others have reported, this mysterious keychain error is most easily observable when launching via Xcode with the debugger attached. Once the error starts occurring, the keychain doesn't seem to right itself regardless of memory pressure until one reboots the device.
When I rebooted my device the error went away, allowing me to continue testing. Not sure what else to do. In my situation, moving to NSUserDefaults or some other storage solution was not a possibility.
One way to get around this issue with the keychain is to use dispatch_async to allow the app to launch. This works when the app is opened from the background. Also make sure you have the kSecAttrAccessibleAfterFirstUnlock accessibility setting on the keychain.
dispatch_async(dispatch_get_main_queue(), ^{
// save/write to keychain
})
I am using GenericKeychain classes from apple:
https://developer.apple.com/library/content/samplecode/GenericKeychain/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007797-Intro-DontLinkElementID_2
struct KeychainConfiguration {
static let serviceName = "MyAppService"
/*
Specifying an access group to use with `KeychainPasswordItem` instances will create items shared accross both apps.
For information on App ID prefixes, see:
https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/AppID.html
and:
https://developer.apple.com/library/ios/technotes/tn2311/_index.html
*/
// static let accessGroup = "[YOUR APP ID PREFIX].com.example.apple-samplecode.GenericKeychainShared"
/*
Not specifying an access group to use with `KeychainPasswordItem` instances
will create items specific to each app.
*/
static let accessGroup: String? = nil
}
In this file I was specifying my custom accessGroup in this line static let accessGroup = "[YOUR APP ID PREFIX].com.example.apple-samplecode.GenericKeychainShared"
After returning to static let accessGroup: String? = nil the problem was gone. :)
Make sure keychain sharing must on under Capabilities in project Target.
In my case the App_Name.entitlements file has different bundle id than of my project bundle id. So, I change bundle id in the App_Name.entitlements file as of my project bundle id.
e.g., Suppose your project bundle id is com.Apple.testApp then got to
App_Name.entitlements file open key Keychain Access Groups key which is of Array type.
Under item0 set value of your project bundle id as eg:- $(AppIdentifierPrefix)com.Apple.testAp.
According to #iCaramba's answer. I've found a workaround:
Stop the task to kill the app (if you are already running the app)
Launch the app on your device manually. DO NOT use Xcode
Use Xcode to re-launch the app

NSUserDefaults boolForKey not working in iOS 8

I have a simple issue but could not find an answer so far. I have a signup view that only appears once when the user first launches the app and once successful it is never shown again. I store this in NSUserDefaults as a BOOL. Here is how I check in the App delegate when the app is first launched:
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"returninguser"] != YES){
// First launch
}
else {
//not first launch
}
The problem I'm having is that at first launch, the [NSUserDefaults standardUserDefaults] boolForKey:#"returninguser"] is returning a YES. This is only an issue on iOS 8. It works fine on iOS 7.
Can anyone help with this please?
Ok, after playing with this some more, I found the answer to my question here:
NSUserDefaults not cleared after app uninstall on simulator
As mentioned above, the problem is that the simulator seems to store NSUserDefaults even after deleting the app. That's why Rob could not reproduce the issue, it works fine the first time you try but if you delete the app and try again it doesn't work.

NSUserDefaults not cleared after app uninstall on simulator

this may sound real NOOB! I want to check if it's the second time the user enters my application, so to keep the run count I'm using NSUserDefaults. I have implemented the following code in my rootViewController's viewDidLoad method:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSLog(#"hello %ld", (long)[userDefaults integerForKey:#"runCount"]);
if ([userDefaults integerForKey:#"runCount"] != 1) {
//not the 2nd run
[userDefaults setInteger:1 forKey:#"runCount"];
NSLog(#"not 2nd run");
} else {
//second run or more
NSLog(#"2nd run");
}
[userDefaults synchronize];
everything works fine, but the problem is that when I uninstall(delete and re-install) the application according to here and here the data should be cleared, but it is not and after re-installing the app previous data is still showing up.
I'm running my app on iOS simulator using xCode6-beta and targeting the application on iOS 8
I think this is due to a bug in the iOS8 Beta Simulator.
The expected behavior is that when the app is deleted, the NSUserDefaults for that app are deleted as well.
However, NSUserDefaults are NOT deleted when you remove an app from the simulator.
They are correctly deleted when you delete them from a physical device running iOS8.
A quick and annoying solution for now is to click, iOS Simulator -> Reset Content and Settings.
Xcode 9.2 with Simulator 10 still presents this issue. Menu option is now Hardware .. Erase All Content and Settings
I submitted a bug report btw
Since Reset Content and Settings is a nuclear option, you could consider two other options until the bug on the iOS 8/Xcode 6 GM simulator is addressed:
You could manually delete the plist file where the NSUserDefaults are stored. This is currently located at ~/Library/Developer/CoreSimulator/Devices/*some_device_id*/Library/Preferences/com.yourcompany.yourapp.plist It's a little tedious to find the right simulator to work with among the UUID directory names. EDIT: 2014-10-28 20-34-52 Correct path:
~/Library/Developer/CoreSimulator/Devices/*some_device_id*/data/Library/Preferences/com.yourcompany.yourapp.plist
You could perform "surgery" on that plist (using a run script build phase perhaps) using plistbuddy e.g.
/usr/libexec/plistbuddy -c "Set :BSDidMoveSqliteDb 0" path_to_plist
For anyone facing the same issue.
If you have more than 1 app under the same group and all of them are using app groups (ON under capabilities), then you will have to remove all the apps from the device in order for the user defaults to be cleared.
Since the user defaults are shared, even if one of the app is on the device then it will not be deleted, as that app will be using the userdefaults.
The code should work fine on the device.
Maybe some bugs in the simulator.
Try to Reset Contents and Settings for the Simulator.
it is a bug, and you can delete NSUserDefaults with following code
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
While this is still a bug another option could be to remove the specific key(s) in NSUserDefaults. Most of the time, when testing/developing, we only care about a few keys and not everything in NSUserDefaults. If you do only care about a few keys than I propose adding removeObjectForKey:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// ADD THIS TO SIMULATE CLEAN/EMPTY DEFAULTS, COMMENT OUT AFTER INITIAL EXECUTION.
// This will cause the TRUE case to be executed.
[userDefaults removeObjectForKey:#"runCount"]
NSLog(#"hello %ld", (long)[userDefaults integerForKey:#"runCount"]);
if ([userDefaults integerForKey:#"runCount"] != 1) {
//not the 2nd run
[userDefaults setInteger:1 forKey:#"runCount"];
NSLog(#"not 2nd run");
} else {
//second run or more
NSLog(#"2nd run");
}
[userDefaults synchronize];
Adding removeObjectForKey simulates the first run of the app, commenting it out will simulate all subsequent app executions.
In my case i found the *.plist in the following directory:
[1] /Users/SOME-USERNAME/Library/Developer/CoreSimulator/Devices/SOME-DEVICE-ID/data/Library/Preferences/SP.UserDefaultsTest.plist
Problem: Deleting the app in xCode 6(iOS 8 simulator) but the file stays on disk like mentioned above.
Solution: Deleting the located file from path [1] manually and the NSUserDefaults are gone.
So the annoying way to reset the simulator is no longer necessary.

Resources