Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use KVO for UserDefaults in Swift?

I'm rewriting parts of an app, and found this code:

fileprivate let defaults = UserDefaults.standard  func storeValue(_ value: AnyObject, forKey key:String) {     defaults.set(value, forKey: key)     defaults.synchronize()      NotificationCenter.default.post(name: Notification.Name(rawValue: "persistanceServiceValueChangedNotification"), object: key) } func getValueForKey(_ key:String, defaultValue:AnyObject? = nil) -> AnyObject? {     return defaults.object(forKey: key) as AnyObject? ?? defaultValue } 

When CMD-clicking the line defaults.synchronize() I see that synchronize is planned deprecated. This is written in the code:

/*!      -synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.       -synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...      - ...before reading in order to fetch updated values: remove the synchronize call      - ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify      - ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)      - ...for any other reason: remove the synchronize call      */ 

As far as I can interpret, the usage in my case fits the second description: synchronizing after writing, in order to notify others.

It suggests using KVO to ovserve, but how? When I search for this, I find a bunch of slightly older Objective-C-examples. What is the best practice for observing UserDefaults?

like image 310
Sti Avatar asked May 14 '17 11:05

Sti


People also ask

How do I access UserDefaults in Swift?

Writing or Setting Values To User Defaults You simply invoke the set(_:forKey:) method on the UserDefaults instance. In this example, we set the value of myKey to true by invoking the set(_:forKey:) method on the shared defaults object.

How data is stored in UserDefaults?

Updating the Prospects initializer so that it loads its data from UserDefaults where possible. Adding a save() method to the same class, writing the current data to UserDefaults . Calling save() when adding a prospect or toggling its isContacted property.

Is UserDefaults fast?

Accessing NSUserDefaults is slower than a variable, because a variable is stored in memory, whereas the NSUserDefaults is fetched from the disk. If you asking about reading speed between variables and defaults then definitely you can read fast from variables.

What can be stored in UserDefaults?

You can use UserDefaults to store any basic data type for as long as the app is installed. You can write basic types such as Bool , Float , Double , Int , String , or URL , but you can also write more complex types such as arrays, dictionaries and Date – and even Data values.


2 Answers

As of iOS 11 + Swift 4, the recommended way (according to SwiftLint) is using the block-based KVO API.

Example:

Let's say I have an integer value stored in my user defaults and it's called greetingsCount.

First I need to extend UserDefaults with a dynamic var that has the same name as the user defaults key you want to observe:

extension UserDefaults {     @objc dynamic var greetingsCount: Int {         return integer(forKey: "greetingsCount")     } } 

This allows us to later on define the key path for observing, like this:

var observer: NSKeyValueObservation?  init() {     observer = UserDefaults.standard.observe(\.greetingsCount, options: [.initial, .new], changeHandler: { (defaults, change) in         // your change logic here     }) } 

And never forget to clean up:

deinit {     observer?.invalidate() } 
like image 89
Michal Avatar answered Sep 22 '22 16:09

Michal


From the blog of David Smith http://dscoder.com/defaults.html https://twitter.com/catfish_man/status/674727133017587712

If one process sets a shared default, then notifies another process to read it, then you may be in one of the very few remaining situations that it's useful to call the -synchronize method in: -synchronize acts as a "barrier", in that it provides a guarantee that once it has returned, any other process that reads that default will see the new value rather than the old value.

For applications running on iOS 9.3 and later / macOS Sierra and later, -synchronize is not needed (or recommended) even in this situation, since Key-Value Observation of defaults works between processes now, so the reading process can just watch directly for the value to change. As a result of that, applications running on those operating systems should generally never call synchronize.

So in most likely case you do not need to set to call synchronize. It is automatically handled by KVO.

To do this you need add observer in your classes where you are handling persistanceServiceValueChangedNotification notification. Let say you are setting a key with name "myKey"

Add observer in your class may be viewDidLoad etc

 UserDefaults.standard.addObserver(self, forKeyPath: "myKey", options: NSKeyValueObservingOptions.new, context: nil) 

Handle the observer

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {      //do your changes with for key } 

Also remove your observer in deinit

like image 33
codester Avatar answered Sep 22 '22 16:09

codester