Is it fine to access NSUserDefaults/UserDefaults frequently? - ios

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.

Related

When to Grab Data From firebase in Swift (iOS dev)

I'm currently creating an app which stores information in a database which the users device must have in order for certain parts of the app to respond differently.
My question is, when should I be calling for this data from firebase.
e.g.
In one part of my app, the app needs to know if the user is currently "connected" to another user. Currently, it checks this against the database as the user presses on the tab bar icon where this information needs to be known, which takes a couple seconds. (checked in the viewdidload() override func)
Should I be grabbing all data from the database before the first view controller is even displayed?
Is there a way to share this between all the view controllers?
If I could load all data from the database into global variables on the device that all view controllers can see this would seem much easier, however i'm not sure if this is good practice.
What would you recommend?
My database structure:
Basically, right now, when the user opens the app and logs in, I need the 'name' and 'family' of each user to be stored for use across the whole app globally across all classes and view controllers.
In terms of the list. when the user clicks the view controller where the list is, currently i'm just running code like this
self.ref.child("familys").child(email.replacingOccurrences(of: ".", with: "")).child("list").observe(.value, with: { (DataSnapshot) in
if DataSnapshot.hasChildren() == false{
print("No list")
return
}
self.tableList = DataSnapshot.value as! [String]
self.tableView.reloadData()
}) { (Error) in
print(Error)
}
then it goes ahead and updates the list with the array 'tableList'.
This means the first time the user clicks to get to the shopping view, there is some delay before the list populates.
I'm not sure what the standard way is to go about grabbing data like this and when it should be done in a way which minimises data usage and database access frequency.
I think you are on the right track. Although you may think you need all the data stored globally so you can access it from all view controllers, you don't. You can pass the data between viewControllers through segues. If you are making a tab based app, you just pull the data necessary for that ViewController in the viewDidLoad() (just like you are doing).
The name of the game is to structure your Firebase database so that when you pull data, you can pull as little as possible to fill all the fields in the View Controller. Since Firebase uses a JSON structure, there is no shame in saving the same data twice in order to make a search faster.
That being said, I think a currentUser global variable is useful in your case. Assuming you have a current user (one user logged in), I would just create a User class that mimics the Firebase and instantiate one global variable called currentUser. Your currentUser object should contain enough information to go and pull anything you need for filling ViewControllers. For example if your User class has an email attribute, you can do:
self.ref.child("familys").child(currentUser.email.replacingOccurrences(of: ".", with: "")).child("list").observe(.value, with: { (DataSnapshot) in
if DataSnapshot.hasChildren() == false{
print("No list")
return
}
self.tableList = DataSnapshot.value as! [String]
self.tableView.reloadData()
}) { (Error) in
print(Error)
}
Global variables should be avoided when possible, but I think just reducing it to one global variable should be enough to get you going.
As for why you are getting a delay, I'm not sure. When you are pulling that little data, it should be extremely fast. If there really is a noticeable delay when pulling a list of 2 items, the issue might be elsewhere (network, simulator, etc.).

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.

Storing check status of toDo list

I am developing a new screen in my app and I wanted to consult with you the best way to do it.
The new screen is going to be a static checklist and I want the user to check "step-by-step" (i.e. line-by-line) that he completed the step.
I have 2 view controller
VC1 = theme static list
VC2 = step static list linked to each step
--> you click on VC1.Theme1 and then it displays VC2.step1,2,3, etc.. in a static tableview.
What I want to do is:
1. checking steps in VC2 and "keep them in memory"
2. when all steps in VC2 are checked updated the status of the theme in VC1
If for 2. it is a simple calculation I am wondering what would be the best method to store the status of the step completion. I am hesitating between a database (custom) or the phone memory... I have not enough experience to know if there are other options or what is the best practice.
Any advise?
I believe the most simple way is to use UserDefaults.
I usually use it for simple data storage. For a start I think it's a good solution for you. For example you may store an array of Bools which indicates whether steps are checked or not. Or store a counter (int) that will indicate quantity of checked steps and compare it to total of steps in the VC2.
How to write to user defaults:
let defaults = UserDefaults.standard()
defaults.set(yourValue, forKey: "valueKey")
And read:
defaults.value(forKey: "valueKey")
Hope it helps.

How to store form data in a swift app

I'm working on my first app and I need some help understanding what kind of data storage I need. I'm familiar with PHP, SQL, and databases but I don't know where to start with this project.
The app is pretty much a basic form with text fields, pickers, and uploaded images. At the end, the user will press submit and all of their data will be sent in an email.
What is the best way to store their data, so the user can go to previous screens and have their previously entered info still there. And then what is the best way to store the data after they press submit to send it in an email?,
Thanks so much for your help!
If it's just form data that you're storing once for submission, for simplicity sake, I recommend just stuffing it in a global dictionary that you can access from different views. Swift makes this easy by just adding an empty swift file and defining your dictionary at the top:
var myFormData: [String: AnyObject]()
You can now access "myFormData" form anywhere in your app, add and remove stuff from it.
You shouldn't technically need to "reload previous views" because of the way the navigation stack works. Anything you go back to should hold it's info.
If you really need to save the data to allow the user to close the app and then pick up where they left off much later, then I recommend simply kicking your dictionary to NSUserDefaults. It doesn't sound like something that needs to involve a database.
You can use a class called NSUserDefaults
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults() //make an instance
defaults.setObject("Bob", forKey: "myName") //storing a value
defaults.synchronize() //synchronize data
println(defaults.objectForKey("myName")!) //retrieve the data

NSUserDefaults transactionality

Is there any way to add transactionality to NSUserDefaults? I would need something like the well known begin - commit - revert functions on database handlers, thus I could revert a modification on the user defaults in some cases. Of course other users of this user defaults would be blocked from writing during the transaction.
Note1: synchronize method of the above class does not do this thing because:
according to the doc, it is called from time to time also by the framework
there is no "revert"
Note2: I saw dictionaryRepresentation and registerDefaults with that I could implement my own transaction mechanism (holding a copy of the old defaults in the memory / even saved to a plist during the transaction). But maybe there is a ready solution for this?
My use case:
I have a wizard-like flow of screens where the user can edit some settings on each screen. As of the current implementation these settings are stored immediately in the defaults as the user moves to the next screen of the wizard. Now this wizard can be interrupted by some other events (even the user can choose to exit/cancel the wizard at any screen) and in this case I would like to roll back the modifications.
One possible solution is to defer setting the values until the end of your wizard. This can be easily done for example using a proxy that will record the messages sent to it and then replay them on the real NSUserDefaults. Recording the messages should be pretty simple:
- (void) forwardInvocation: (NSInvocation*) invocation
{
[invocations addObject:invocation];
}
Where invocations is a mutable array. Replaying the messages back is also simple:
- (void) replayOnTarget: (id) target
{
for (NSInvocation *op in invocations)
[op invokeWithTarget:target];
}
This way the wizard does not have to know anything about the transactions. It would get the recording proxy instead of the expected NSUserDefaults instance and send the messages as usual. After the calling code knows the wizard succeeded, it can replay the messages from the proxy on the shared user defaults. (I have added some sample code on GitHub.)
Maybe this is overkill, but since the recording proxy is generic and can be used in other cases, maybe it’s not bad. Same thing can also be done using blocks:
[transaction addObject:[^{
[defaults setObject:… forKey:…];
} copy]];
Where transaction is a mutable array, again. When the wizard succeeds, you would simply iterate over the array and execute the stored blocks.

Resources