UserDefault return different values - ios

I have a very weird problem in Swift3. I want to keep an user logged into application after he has already authenticated in his last session.
My problem is that UserDefaults return sometimes true, sometimes false even if is logged in his account. The problem makes me crazy. I use an bool value stored in UserDefaults, I tried to save a specific string but the problem persist.
Anyone had this problem? Any solutions?
Here is the code when I log in:
UserDefaults.standard.set(true, forKey: LOGIN)
And this is my code in AppDelegate in didFinishLaunchingWithOptions method:
if UserDefaults.standard.bool(forKey: LOGIN) {
AppData().updateUserInformation()
}

I suggest calling UserDefaults.standard.synchronize() after setting UserDefaults values. A lot of developers say that you don't need to do this and that iOS will take care of it for you.
But I've found that not always to be the case, especially when reading values shortly after setting them, or if the app exits before they are synchronized and therefore are lost.

Related

set value in UserDefaults synchronously

I am trying to save boolean in UserDefault in swift.
so when I set value in userDefault, my very next instruction is to switch to view controller and close the current view controller.
so, what is happening now is, sometimes userDefault saves the value in DB, and sometimes it doesn't.
I read documentation from Apple
https://developer.apple.com/documentation/foundation/userdefaults
and found that
At runtime, you use UserDefaults objects to read the defaults that your app uses from a user’s defaults database. UserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value. When you set a default value, it’s changed synchronously within your process, and asynchronously to persistent storage and other processes.
so, I guess because in the very next line I open a new controller and close the current one so due to which there is inconsistency.
here is my code
func setWalkthroughShown(completionHandler: #escaping ()->()) {
UserDefaults.standard.set(true, forKey: isWalkthroughCompleted)
UserDefaults.standard.synchronize()
completionHandler()
}
I even called UserDefaults.standard.synchronize() so that operation may become synchronous.
even though in the documentation it is clearly written not to use this function.
can someone please guide me where I am wrong? how can I save across all places before closing the current process?
this is the function by which I am retrieving value
func isWalkthroughShown() -> Bool {
return UserDefaults.standard.bool(forKey: isWalkthroughCompleted)
}
here isWalkthroughCompleted is a string and you can see I am using same string for saving and retrieving value
Actually, there was no syntax error in coding.
I was actually testing it in the wrong way.
after submitting for the request of saving data in userdefaults, I was recompiling immediately and as value stores asynchronously so sometimes due to killing of the process I was getting this issue.
thanks to #matt.
for detail iOS UserDefaults falls behind saved content

How to make app (with username/password login) know that a user is logged in?

I'm trying to make a login screen and made a database with Firebase by Google. The way I tried to make accounts in database is just by making a new child which is "Users" and then the next child would be a user (which is uniquely defined with a unique username). Then, user has other attributes and an attribute called loggedIn which is set to String 'false' but when on loginScreen login goes successfully set to String 'true'. How can I know after login and when that LoginViewController goes away which account has logged in exactly on that phone (simulator at the time) because there can be more users at one time with the attribute value 'loggedIn' set to 'true' and because of that can't go back to database to check that. I'm really new at this and don't know if this whole approach is okay by making in real-time database something like that and actually checking that by the attribute. Maybe I have to use some kind of a local database or something similar?
Swift 4, iOS development, Xcode 9
If you just want to persist locally the login status you may use UserDefaults. Just have a key called currentUser and set the value as his unique Id.
Once a user logs out just clear that key.
You can also use that to determine whether to launch the login screen when the app opens.
But it may be better to use something like AWS Cognito or similar services to handle user management.
Are you building an iOS app only or a cross-platform project?
I'm not sure this is the perfect answer for you. However, some of my apps use CloudKit and the login is authenticated and only then opens up areas of the app on the user's device. I'm sure if you wanted to check the status of successful logins, the authentication on the cloud could update as confirmation and you could subsequently access the cloud database using the CloudKit Dashboard.
It's quite intuitive and works seamlessly with Xcode.
I have a class called LoginManager, defined as an NSObject. Included in there is an optional String variable called accessToken with setter and getter functions for saving and retrieving my access token, including an option for clearing the token which would be used upon logout. There is also a function to check the status of the key.
var accessToken: String? {
get {
return //key from storage
}
set {
if newValue == nil {
//remove key from storage
} else {
//save key to storage
}
}
}
func isUserLogedIn() -> Bool {
return self.accessToken != nil
}
In my own case, I have an application manager object that changes the root view controller based on this info.
LoginManager.singleton.isUserLogedIn() ? loadMainView() : loadLoginView()
So the resulting view controller is never loaded unless the user is logged in. Is that useful to you?

Is it fine to access NSUserDefaults/UserDefaults frequently?

I have a login view controller in which the user enters their preferences like whether or not he wants to activate certain UI features.
I store these as variables whose getters and setters directly access UserDefaults, here is an example of one of these:
class Preferences {
static var likesSpaghetti : Bool {
set (likesSpaghetti) {
UserDefaults.standard.set(likesSpaghetti, forKey: "likesSpaghetti")
}
get {
return UserDefaults.standard.bool(forKey: "likesSpaghetti")
}
}
}
So that whenever I want to set any of these I simply write something like this:
Preferences.likesSpaghetti = false
Now, my question is: Can I set these variables every time the user flicks the on/off switch or should I keep the preference represented as a local variable and then only set:
Preferences.likesSpaghetti = spaghettiSwitch.isOn
when the user segue's away from the loginViewController? Is every access of UserDefault instant and quick? or is it laggy and should be used mercifully?
Edit after closing this question: So I learned to not prematurely optimize, and that it is probably ok within the scope of a few dozen elements. So I should be fine. I'm going to just update every time the user modifies anything so that my code is a lot easier to read and maintain.
Thanks everyone!
Your code is just fine. Don't worry about such optimizations until you actually encounter an issue. Trust that UserDefaults is implemented smartly (because it is). There is nothing "laggy" about setting something as simple as a Bool in UserDefaults.
You also wish to review another one of my answers which is related to this question: When and why should you use NSUserDefaults's synchronize() method?
Actually userDefaults (it's originally a plist file) is used for this purpose which is storing app settings and that light-wight content creating a variable may consum memory if you have to configure many of them , besides not reflecting the new setting change directly to defaults made by user , may cause un-expectable old settings to happen at the time of change such as a localized alert or part of code (such as push notification callback) that check the same setting where the user thinks it's already reflected
Adding to both #rmaddy #Sh_Khan, if you think about the security aspect of it, NSUserDafault is exactly for the details which is app specific such as settings, preferences or some configurations which are not security sensitive while things like passwords, usernames and sensitive data are not recommended to store in UserDefaults. You should use services like keychain which is encrypted for such data.

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.

Removing a key from NSUserDefaults not working

I have a logout function in my app. Seems to be a weird problem where it doesn't save the NSUserDefaults. Here I simply want to remove the key. However after logging out if I then open the app again it will find that this key is still in the NSUserDefaults.
func didLogout() {
// Clear user data
let settings = NSUserDefaults.standardUserDefaults()
settings.removeObjectForKey("userData")
settings.synchronize()
unregisterForRemoteNotifications()
openLoginScreen()
}
Any ideas what I could be doing wrong here?
try this
DispatchQueue.main.async {
UserDefaults.standard.removeObject(forKey: YOUR_KEY_HERE)
}
helped for me
removeObjectForKey(_:)
Removing a default has no effect on the value returned by the
objectForKey: method if the same key exists in a domain that precedes
the standard application domain in the search list.
Just use another key instead of userData. It might exists in another domain.
The code above is correct. The key will be still there, but it will return only nil value. So, when user logout you can set
NSUserDefaults.standardUserDefaults().removeObjectForKey("userData")
and when new user login you can set new value by checking
if NSUserDefaults.standardUserDefaults().objectForKey("userData") == nil
One of our (XCTest) unit tests was failing every other run. It turned out that -removeObjectForKey: was — inexplicably — only working every other run. I verified this with defaults.dictionaryRepresentation before and after -removeObjectForKey:. I thought perhaps the keys were being added to two domains and the first remove wasn't getting them both (the docs say this can happen) so I cleverly added a second remove, but that also had no effect. My solution was to set the key to the uninitialized value instead.
Any ideas?
There is no issue in your above code you might have set data in app delegate or when you login your app, or you have mistyped key value.
If you want to clear all data. This will Work
let appDomain = NSBundle.mainBundle().bundleIdentifier!
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(appDomain)
I did the following to delete the userdefault of the app on user loggout
private static let userDefaults = NSUserDefaults.standardUserDefaults()
private static let userTokenKey = "userTokenKey"
userDefaults.removeObjectForKey(userTokenKey)
userDefaults.synchronize()

Resources