18

I'm trying to use the UserDefaults to persistently save a boolean value. This is my code :

public static var isOffline = UserDefaults.standard.bool(forKey: "isOffline") {
    didSet {
        print("Saving isOffline flag which is now \(isOffline)")
        UserDefaults.standard.set(isOffline, forKey: "isOffline")
        UserDefaults.standard.synchronize()
    }
}

Why doesn't work? What is the problem in this code?

The problem is that when I try to retrieve "isOffline" key from UserDefaults I always get a false.

I set the isOffline in the .onChange method of the row (I'm using Eureka as framework to create forms). The flag keep the right value during the lifecycle of the app, but when I close it that value is probably removed in some way.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Andrea Mario Lufino
  • 7,921
  • 12
  • 47
  • 78
  • In what way doesn't your code work? Please show us a [mcve]. – Hamish Dec 23 '16 at 13:37
  • 1
    Also note that you don't need to call `synchronize()` every time you change a value in the user defaults – only when you specifically *need* the system to save the user defaults immediately (e.g when your app is about to be terminated) – see [this Q&A](http://stackoverflow.com/questions/40808072/when-and-why-should-you-use-nsuserdefaultss-synchronize-method). – Hamish Dec 23 '16 at 13:48
  • Yes, you're right. But now the problem is that the "isOffline" flag always returns false – Andrea Mario Lufino Dec 23 '16 at 13:51
  • How and where are you setting `isOffline`? – Hamish Dec 23 '16 at 13:51
  • @Hamish I've added more details – Andrea Mario Lufino Dec 23 '16 at 13:56
  • I assume by "The flag keep the right value during the lifecycle of the app" you mean that the `print` statement is successfully printing out true when you update the value? Are you updating the value for the "isOffline" key anywhere else in your app? Although note that I would agree with Venkat's suggestion of using a computed property for this, as a stored property won't be updated if the user default for "isOffline" is changed from elsewhere in your program – although that doesn't explain why your current code is failing to save values. – Hamish Dec 23 '16 at 14:15
  • The value is not changed anywhere else in the app. – Andrea Mario Lufino Dec 23 '16 at 14:18
  • Xcode 8 simulator had a problem with saving data to `UserDefaults`, and it was fixable by rebooting `mac os`. Maybe that's the case? http://stackoverflow.com/questions/37824190/why-wont-my-app-run-in-xcode-8-beta-8s128d/37824276#37824276 – user28434'mstep Dec 23 '16 at 14:38
  • I found the problem. UserDefaults can be saved only on the main thread. I wrote the line which saves the isOffline based on the value of the row outside the onChange block putting it in the NSUserDefaults and everything works good – Andrea Mario Lufino Dec 23 '16 at 15:17

4 Answers4

31

I had the same problem and the issue was in the "didSet" block itself. I don't know why, but it does not work with userDefaults - it does not persist it properly and after killing the application all changes were gone.

Synchronize() does not help. I found out, this method is no longer necessary and it will be deprecated in future (this is comment in UserDefaults class):

-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.

By trial and error I found out, that it works, if I call it from main thread:

public static var isOffline = UserDefaults.standard.bool(forKey: "isOffline") {
    didSet {
        print("Saving isOffline flag which is now \(isOffline)")
        DispatchQueue.main.async {
            UserDefaults.standard.set(isOffline, forKey: "isOffline")
        }
    }
}

If anyone can explain, why it works on main thread and no other, I would be glad to hear it.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Vojtech Kubat
  • 411
  • 4
  • 6
  • 2
    "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." It might be because of the cache and making changes on the main thread might be the reason that the defaults is written instantaneously. – Ariq Dec 08 '18 at 21:54
6

try to change

UserDefaults.standard.set(isOffline, forKey: "isOffline")

to

UserDefaults.standard.setValue(isOffline, forKey: "isOffline")

without the dispatch code

Omar N Shamali
  • 503
  • 5
  • 11
  • 3
    Do you know why the set() function doesn't work? Because it seems kind of random on my devices. Sometimes it works, sometimes it works for a few values. Sometimes it doesn't. The strange thing is. Sometimes I store the values, then load them and get nothing. Then I restart the app and suddenly some of the values are back. With setValue it looks like those problems went away. But I would really like to know WHY! :) – LetzFlow Mar 31 '21 at 13:28
  • i am sorry, i dont know the reason, as I'm happy its working for all of us. Apple works in mysterious ways :p – Omar N Shamali Apr 02 '21 at 23:21
  • Yeah, the problem went away with `setValue` than `set`. – Ahmed Shendy Aug 15 '23 at 07:37
2

Do Like this,

public static var isOffline:Bool {
    get {
       return UserDefaults.standard.bool(forKey: "isOffline")
    }
    set(newValue) {
        print("Saving isOffline flag which is now \(isOffline)")
        UserDefaults.standard.set(newValue, forKey: "isOffline")
        UserDefaults.standard.synchronize()
    }
}
Venk
  • 5,949
  • 9
  • 41
  • 52
  • 2
    Your first example won't work as the so called "`newValue`" you're binding is actually the *old value* of the property. But neither example answers OP's question of "What is the problem in this code?". – Hamish Dec 23 '16 at 13:41
  • First example also works. I personally tested and then only I posted. – Venk Dec 26 '16 at 04:50
  • 1
    I've been struggling and debugging for days... and all it needed was .synchronize – MLBDG May 17 '17 at 20:58
  • 1
    However, if you take a look at the function description https://developer.apple.com/documentation/foundation/nsuserdefaults/1414005-synchronize : "Waits for any pending asynchronous updates to the defaults database and returns; this method is unnecessary and shouldn't be used." – tryp Jan 10 '18 at 10:37
2

Don't ask me why, but I had this issue also in a project in Xcode 14.

After hours of debugging I realized that the length of my key exceeded 18 characters, it would no longer be saved. However, I couldn't reproduce this in a vanilla project, so I guess it had something to do with the project.

But anyway: if you're experiencing this, try decreasing the character count of your key.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Giel Berkers
  • 2,852
  • 3
  • 39
  • 58