I want to reset all my global variables when a user logs out as otherwise some of their information will stay (the info in global variables).
Is there any way to do this without manually resetting them to their initial value when the log out button is pressed?
If I understand you correctly, you are saving the user-data into a global variable? That doesn't seem to make sense for me.
If you are intending to equal NSUserDefaults as global variables, you can use the following approach to delete the Data from your standard UserDefaults:
private func cleanUserDefaultsOnLogout() {
let standardDefaults = UserDefaults.standard
for key in standardDefaults.dictionaryRepresentation().keys {
standardDefaults.removeObject(forKey: key)
}
standardDefaults.synchronize()
}
Please correct me if I've misinterpreted your question.
extension UserDefaults {
class func clean() {
guard let aValidIdentifier =
Bundle.main.bundleIdentifier else { return }
self.standard.removePersistentDomain(forName:
aValidIdentifier)
self.standard.synchronize()
}
}
use it
UserDefaults.clean()
Related
why is this not working? It's made to check if a variable with the name "language" exists at the UserDefaults.
func isKeyPresentInUserDefaults(key: String) -> Bool {
return UserDefaults.standard.object(forKey: key) != nil
}
if isKeyPresentInUserDefaults(key: language!) == true { //<-- Cannot find 'language' in scope
print("Language variable: true")
}else {
print("Language variable: false")
}
I tried to add this before the error:
let language: String?
... but it also hadn't worked.
Thank you for everyone who can help!
UserDefaults is about storing and retrieving value of variable (not it's name) using chosen key, being some String. You store the value of variable using chosen key, and then retrive stored value using the same key. What about using something like this?
let language = "language"
isKeyPresentInUserDefaults will return true if you (someone) previously stored value in UserDefaults using:
UserDefaults.default.set(someVariable, forKey: language)
Some complication (to be read as soon as text above is clear): Of course, if value of someVariable was nil, function returns false, as value nil is stored in UserDefaults. But I suppose you're not going to store Optional variable with value nil in UserDefaults.
First of all, thank you for your support. I found my own way to fix that. Hopefully it's well enough so that others later also can use this.
First, I created a new class called "UserDefaultsMonitoring.swift"
import Foundation
final class UserDefaultsMonitor {
static let shared = UserDefaultsMonitor()
public private(set) var isLanguage: Bool = false
public func startMonitoring() {
self.isLanguage = UserDefaults.standard.object(forKey: "Language") != nil
}
}
Then I added to the already by xCode created class called "ExtentionDelegate.swift" the following:
func applicationDidBecomeActive() {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
UserDefaultsMonitor.shared.startMonitoring()
}
And last but not least I used this to check if the searched key exists:
if !UserDefaultsMonitor.shared.isLanguage {
NoLanguageSet()
}
I am trying to mock UserDefaults to be able to test its behaviour.
What is the best way to do it without corrupting the real computed property that save the user token key?
class UserDefaultsService {
private struct Keys {
static let token = "partageTokenKey"
}
//MARK: - Save or retrieve the user token
static var token: String? {
get {
return UserDefaults.standard.string(
forKey: Keys.token)
}
set {
UserDefaults.standard.set(
newValue, forKey: Keys.token)
}
}
}
You can subclass UserDefaults :
(source)
class MockUserDefaults : UserDefaults {
convenience init() {
self.init(suiteName: "Mock User Defaults")!
}
override init?(suiteName suitename: String?) {
UserDefaults().removePersistentDomain(forName: suitename!)
super.init(suiteName: suitename)
}
}
And in UserDefaultsService instead of directly accessing UserDefaults.standard you can create property based on the target that you are running. In production/staging you can have UserDefaults.standard and for testing you can have MockUserDefaults
You should add PREPROCESSOR Flag before using them
#if TESTING
let userDefaults: UserDefaults = UserDefaults.standard
#else
let userDefaults: UserDefaults = MockUserDefaults(suiteName: "testing") ?? UserDefaults.standard
#endif
One way of doing it is to wrap your UserDefaults in a protocol and expose what you need.
Then you create a an actual class which conforms to that protocol and which uses UserDefaults
You can then instantiate your UserDefaultsService with that class.
When you need to test, you can create a mock conforming to the same protocol and use that instead. That way you won't "pollute" your UserDefaults.
The above might seem like a bit of a mouthful so lets break it down.
Note that in the above I removed the "static" part as well, it didn't seem necessary, and it made it easier without it, hope that is OK
1. Create a Protocol
This should be all you are interested in exposing
protocol SettingsContainer {
var token: String? { get set }
}
2. Create an Actual Class
This class will be used with UserDefaults but it is "hidden" behind the protocol.
class UserDefaultsContainer {
private struct Keys {
static let token = "partageTokenKey"
}
}
extension UserDefaultsContainer: SettingsContainer {
var token: String? {
get {
return UserDefaults.standard.string(forKey: Keys.token)
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.token)
}
}
}
3. Instantiate UserDefaultsService With That Class
Now we create an instance of your UserDefaultsService which has an object conforming to the SettingsContainer protocol.
The beauty is that you can change the provided class later on...for instance when testing.
The UserDefaultsService does not know - or care - whatever the SettingsContainer actually does with the value, as long as it can give and take a token, then the UserDefaultsService is happy.
Here's how that looks, note that we are passing a default parameter, so we don't even have to pass a SettingsContainer unless we have to.
class UserDefaultsService {
private var settingsContainer: SettingsContainer
init(settingsContainer: SettingsContainer = UserDefaultsContainer()) {
self.settingsContainer = settingsContainer
}
var token: String? {
get {
return settingsContainer.token
}
set {
settingsContainer.token = newValue
}
}
}
You can now use a new UserDefaultsService like so:
let userDefaultsService = UserDefaultsService()
print("token: \(userDefaultsService.token)")
4 Testing
"Finally" you say :)
To test the above, you can create a MockSettingsContainer conforming to the SettingsContainer
class MockSettingsContainer: SettingsContainer {
var token: String?
}
and pass that to a new UserDefaultsService instance in your test target.
let mockSettingsContainer = MockSettingsContainer()
let userDefaultsService = UserDefaultsService(settingsContainer: mockSettingsContainer)
And can now test that your UserDefaultsService can actually save and retrieve data without polluting UserDefaults.
Final Notes
The above might seem like a lot of work, but the important thing to understand is:
wrap 3rd party components (like UserDefaults) behind a protocol so you are free to change them later on if so needed (for instance when testing).
Have dependencies in your classes that uses these protocols instead of "real" classes, that way you - again - are free to change the classes. As long as they conform to the protocol, all is well :)
Hope that helps.
A very good solution is to not bother creating a mock or extracting a protocol. Instead init a UserDefaults object in your tests like this:
let userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
Now you can go ahead and use the UserDefaults keys you already have defined in an extension and even inject this into any functions as needed! Cool. This will prevent your actual UserDefaults from being touched.
Brief article here
Instead of mocking UserDefaults, you should check the value you persist in UserDefaults, should get retrieved from the same user defaults by accessing using same key. the test case should look like this.
func testWhenTokenIsSavedInUserDefaults_ReturnSameTokenAndVerify {
let persistenceService = UserDefaultsService()
persistenceService.token = "ABC-123"
let defaults = UserDefaults.standard
let expectedValue = defaults.value(forKey: "partageTokenKey")
XCTAssertEquals(persistenceService.token, expectedValue)
}
I'm pretty new to this, but I've been managing to stumble my way through to getting the current location of my IOS device...etc The only problem is, I can't seem to get the GMSPlace to stay assigned to the property I declared at the top of my class, which I plan on using in another function.
It works fine when I run a print statement from within the scope of the callback, but when I seem to use the value stored in 'queryPlace', it returns a 'nil'. I'm guessing its a scope and lifetime issue, but I'm not sure if I understand it properly.
Here is the code that I'm having difficulty with understanding why it won't hold the value of 'place':
import UIKit
import CoreLocation
import GoogleMaps
import GooglePlaces
class GoogleMapSearchVC : UIViewController, CLLocationManagerDelegate {
var placeQuery: GMSPlace?
func loadCurrentPosition() {
print("Loading Positions and Coords")
// Invoke Callback method to get GMSPlacesLiklihood
placesClient.currentPlace(callback: { (placeLikelihoodList, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
if let placeLikelihoodList = placeLikelihoodList {
let place = placeLikelihoodList.likelihoods.first?.place
if let place = place {
self.updateMap(newLocation: place)
// updateMap function moves camera to current location.
self.placeQuery = place
// If I print(self.placeQuery) here, it works fine, but later on placeQuery returns nil.
}
}
})
}
func doSomethingWithPlace() {
print(self.placeQuery?coordinate.latitude)
// Returns 'nil'
}
}
Thank you in advance for any help, very much appreciated.
No, there shouldn't be any "lifetime" issues. placeQuery is bound to the lifetime of your view controller instance.
Just a stupid guess (sorry if that was obvious): Are you sure that doSomethingWithPlace() accesses the variable after the callback has returned? Since you're setting it only in the asynchronous callback, the variable will not be set when loadCurrentPosition() returns.
If that's not the issue, here's a debugging tip to find out where this value is set back to nil: You can set a breakpoint at the line of the variable declaration and the debugger will break in the setter. If you're more into "print debugging", you can also add a didSet clause:
var placeQuery: GMSPlace? {
didSet {
print("setting placeQuery from \(oldValue) to \(placeQuery)")
}
}
tldr; why do we always use UserDefaults.standard instead of subclassing UserDefaults to make something that more precisely fits our needs?
Has anyone out there subclassed UserDefaults before? Or is that considered bad practice?
Say, for example, that we make a ColorDefaults subclass of UserDefaults. When the app, the ColorDefaults object is instantiated, and that object loads all its own data. And the loaded data can then by sent to an appropriate object via delegation, or made universally available via a singleton.
My running theory is that UserDefaults is only meant to store relatively amounts of data, so having to use a singleton enforces that idea.
Bottom line: do we use UserDefaults.standard because:
subclassing is frowned upon
we're supposed to avoid saving too much data to UserDefaults in general
there's just not much value in subclassing anyway?
pretty much anything else.
Your ColorDefaults should not be a subclass of UserDefaults. It should be a plain struct or class with computed properties that are backed by UserDefaults.
Here is an example using static properties but you could refactor this to use a singleton class instead.
struct ColorDefaults {
static var someDefault: String {
get {
return UserDefaults.standard.string(forKey: "someKey") ?? "some initial value"
}
set {
UserDefaults.standard.set(newValue, forKey: "someKey")
}
}
}
let someVal = ColorDefaults.someDefault // read
ColorDefaults.someDefault = "hello" // write
This would also be useful if one of your defaults was more complicated and needed to be encoded/decoded for UserDefaults. The logic goes in here and not all over your app.
Note that such a class should only be used to store small bits of preferences, not full blown app data.
User defaults are a system of storage on file. There is little sense in subclassing unless you want to change some of its logic. But you can create multiple suits like UserDefaults(suiteName: String). What do you expect you would do with subclassing? You could simply just globally define let myDefaults = UserDefaults(suiteName: String) and use it anywhere. I guess you could use methods like
class MyDefaults: UserDefaults {
func saveName(_ name: String) {
setValue(name, forKey: "name_key")
}
}
But then again it might make more sense to just create an extension
extension UserDefaults {
func saveName(_ name: String) {
setValue(name, forKey: "name_key")
}
}
Or make it a bit more complex:
extension UserDefaults {
struct User {
static let defaults = UserDefaults(suiteName: "User")
static func saveName(_ name: String) {
defaults.setValue(name, forKey: "name")
}
}
struct General {
static let defaults = UserDefaults.standard
static func saveLastOpened(date: Date) {
defaults.setValue(date, forKey: "last_opened")
}
}
}
But all of these have one fatal flow: Now you are dependent on using user defaults within the app. At some point you may find the need to rather save these data in some other form like a local JSON file synced with iCloud. I guess UserDefaults.User could be modified to do so but would be very ugly. What we want is not UserDefaults.User.saveName("My name") but User.saveName("My name"). From the interface perspective we do not care where this user name is saved and if a new system is introduced to save these data we don't want the change in interface.
In other words, imagine you are using UserDefaults.User.saveName on 100 places in your application and now want to use another system for saving user name. You will now need to change your code on 100 places to use AnotherSystem.User.saveName while if you simply use User.saveName the interface is still valid.
So the bottom line is there is no sense in (extensively) modifying, extending or subclassing UserDefaults because it is better creating a system that wraps UserDefaults and may later be changed to any other system.
Seems you are looking for something like this
class ColorDefaults : NSObject
{
/// Save Data
class func saveDataInDefaultForKey(_ key: String, _ data: Any){
UserDefaults.standard.set(data, forKey: key)
}
/// Retrieve data
class func retrieveDataFromDefaultsWithKey(_ key: String) -> Any {
return UserDefaults.standard.value(forKey: key) as Any
}
}
Save and get data:
/// Save Data
ColorDefaults.saveDataInDefaultForKey("myArray", myArray)
ColorDefaults.saveDataInDefaultForKey("myString", myString)
/// Get Data
if let valueString = ColorDefaults.retrieveDataFromDefaultsWithKey("myString") as? String {
print("Saved Value String: \(valueString)")
}
else {
print("Error retrieving myString")
}
if let valueArray = ColorDefaults.retrieveDataFromDefaultsWithKey("myArray") as? [String] {
print("Saved Value Array: \(valueArray)")
}
else{
print("Error retrieving myArray")
}
Output:
I have an app managing a simple stocks portfolio. Amongst other things, it keeps a record of the required exchange rates in a dictionary, like so:
[ EURUSD=X : 1.267548 ]
This disctionary is a Dictionary property of a singleton called CurrencyRateStore.
When updating the stocks quotations, it checks for an updated exchange rate and updates the dictionary with the following code:
CurrencyRateStore.sharedStore()[symbol] = fetchedRate.doubleValue
That calls:
subscript(index: String) -> Double? {
get {
return dictionary[index]
}
set {
// FIXME: crashes when getting out of the app (Home button) and then relaunching it
dictionary[index] = newValue!
println("CurrencyRateStore - updated rate for \(index) : \(newValue!)")
}
}
The first time the app is started, it works fine.
But if I quit the app (with the Home button) and then relaunch it, the currency rates are updated again, but this time, I get a EXC_BAD_ACCESS at the line
dictionary[index] = newValue!
Here is a screenshot:
[EDIT] Here is the thread in the debug navigator:
I tried to update the dictionary without a subscript, like so:
CurrencyRateStore.sharedStore().dictionary[symbol] = fetchedRate.doubleValue
but without more success. Same if I use the function updateValue:forKey:
I didn't have the issue in Objective-C.
Thanks for your help !
[EDIT] Here is the whole class CurrencyRateStore:
class CurrencyRateStore {
// MARK: Singleton
class func sharedStore() -> CurrencyRateStore! {
struct Static {
static var instance: CurrencyRateStore?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = CurrencyRateStore()
}
return Static.instance!
}
// MARK: Properties
/** Dictionary of currency rates used by the portfolio, presented like [ EURUSD=X : 1.3624 ] */
var dictionary = [String : Double]()
/** Returns a sorted array of all the keys on the currency rates dictionary */
var allKeys: [String] {
var keysArray = Array(dictionary.keys)
keysArray.sort {$0 < $1}
return keysArray
}
init() {
if let currencyRateDictionary: AnyObject = NSKeyedUnarchiver.unarchiveObjectWithFile(currencyRateArchivePath) {
dictionary = currencyRateDictionary as [String : Double]
}
}
subscript(index: String) -> Double? {
get {
return dictionary[index]
}
set {
// FIXME: crashes when getting out of the app (Home button) and then relaunching it
// (ApplicationWillEnterForeground triggers updateStocks)
dictionary[index] = newValue!
println("CurrencyRateStore - updated rate for \(index) : \(newValue!)")
}
}
func deleteRateForKey(key: String) {
dictionary.removeValueForKey(key)
}
/** Removes all currency rates from the Currency rate store */
func deleteAllRates()
{
dictionary.removeAll()
}
// MARK: Archive items in CurrencyRateStore
var currencyRateArchivePath: String { // Archive path
var documentDirectories: Array = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
// Get the only document directory from that list
let documentDirectory: AnyObject = documentDirectories.first!
return documentDirectory.stringByAppendingPathComponent("currencyRates.archive")
}
func saveChanges()-> Bool
{
// return success or failure
return NSKeyedArchiver.archiveRootObject(dictionary, toFile: currencyRateArchivePath)
}
}
This looks to me like a concurrency issue. Swift dictionaries aren't thread safe, and using them from a singleton can lead to multiple reader/writer issues.
Edit: I am pretty sure this is the real answer, based on the given source/debugging dump. To correct what I wrote, specifically MUTABLE dictionaries and arrays (as well as NSMutableDictionary and NSMutableArray) aren't thread safe, and problems arise when using them within Singletons that are accessed from multiple threads, and that appears to be what the sample source code is doing, or enabling other parts of the code to do.
I don't have an Apple link discussing Swift collection class thread safety, but I"m pretty sure common knowledge. But the following tutorial on Grand Central Dispatch discusses the problem in depth and how to solve it using GCD.
http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1
The error, and the line itself:
dictionary[index] = newValue!
makes me think the problem is newValue being nil - and the error is caused by the forced unwrapping.
I would suggest to set a breakpoint and check its value, or otherwise print it before adding to the dict.
Moreover, it wouldn't be a bad idea to protect that statement with an optional binding:
if let value = newValue {
dictionary[index] = value
}
because if the value type is optional, it can be nil.
So in the end, I contacted Apple Technical Support.
They couldn't reproduce the issue.
I thought that maybe I don't need to save the currency rates, because during the quotes update, the function will check which currency rates it needs anyway, and repopulate the dictionary as needed.
So I deactivated the methods i created to save the CurrencyRateStore and to reload it again with NSKeyedUnarchiver.
Apparently, the crash is gone!