How to use KVO for UserDefaults in Swift?
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()
}
Using NSKeyValueObservation to observe value in UserDefaults
Yes its possible.First of all you need to define keypath as
extension UserDefaults
{
@objc dynamic var isRunningWWDC: Bool
{
get {
return bool(forKey: "isRunningWWDC")
}
set {
set(newValue, forKey: "isRunningWWDC")
}
}
}
And use that keypath for block based KVO as
var observerToken:NSKeyValueObservation?
observerToken = UserDefaults.standard.observe(\.isRunningWWDC, options:[.new,.old])
{ (object, change) in
print("Change is \(object.isRunningWWDC)")
}
UserDefaults.standard.isRunningWWDC = true
Triggering KVO from Push Extension to App
The UserDefault
for suite is not shared instance, so you have to keep it, otherwise it is just released.
Here is fixed App side controller. Tested with Xcode 11.4 / iOS 13.4
Note: KVO works only if really new value is set, so if in your extension you set the equal value into UserDefaults, the observer won't be notified.
class ViewController: UIViewController {
// keep suite instance as member
let userDefaults = UserDefaults(suiteName: "group.com.test.apps")
override func viewDidLoad() {
super.viewDidLoad()
userDefaults!.addObserver(self, forKeyPath: "User", options: [.new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let user = userDefaults!.string(forKey: "User")
NSLog("User \(user)")
}
}
KVO with shared NSUserDefaults in Swift
As of iOS 10 you can use KVO on User Defaults.
This has already been answered around here so I will not re-address it.
Old Answer (iOS 9 and older)
The short answer is that you can't use KVO or even NSNotificationCenter on NSUserDefaults
to communicate changes between an App Extension and the containing App.
There's a great post by Atomic Bird that looks at the ways of coordinating communication. In particular its interesting to look at his analysis of communicating user defaults changes:
A possible alternative for app/extension notifications is to use the
Darwin notification center via
CFNotificationCenterGetDarwinNotifyCenter, which actually is a little
bit like NSDistributedNotificationCenter. There's some discussion of
this by Wade Spires at Apple's dev forums site.I say "possible" because I'm not 100% confident of this continuing to
work. In the documentation for this method, Apple notes thatAn application has only one Darwin notification center, so this
function returns the same value each time it is called.So although this is apparently legal, it also sounds a lot like it
violates the philosophy inherent in app extension restrictions, viz,
that they can't access anything from the hosting app. This is why
[UIApplication sharedApplication] is off limits in extensions. I can't
help wonder if allowing CFNotificationCenterGetDarwinNotifyCenter is
an oversight that might get "fixed" at some point.
So I guess for now a good solution might be to use MMWormhole as they implement the above solution.
Your other option is to use to check the user defaults every time your App becomes active and confirm whether any keys have changed, posting the relevant notifications etc.
Good luck
Trying to observe a string in UserDefaults, but struggling with compile errors
You can try this.
extension UserDefaults {
@objc dynamic var language: String {
get { self.string(forKey: "language") ?? "en" }
set { self.setValue(newValue, forKey: "language") }
}
}
class MyObject {
var observer: NSKeyValueObservation?
init() {
observer = UserDefaults.standard.observer(\.language, options: [.new], changeHandler: { (defaults, change) in
// your change logic here
})
}
deinit {
observer?.invalidate()
}
}
UPDATE
import Foundation
extension UserDefaults {
@objc dynamic var language: String {
get { self.string(forKey: #function) ?? "en" }
set { self.setValue(newValue, forKey: #function) }
}
}
class TopViewModel: NSObject {
let defaults = UserDefaults.standard
let languageKeyPath = #keyPath(UserDefaults.language)
override init() {
super.init()
defaults.addObserver(self, forKeyPath: languageKeyPath, options: .new, context: nil)
let language = defaults.language
print("initialLanguage: \(language)")
defaults.language = "en"
defaults.language = "fr"
}
deinit {
defaults.removeObserver(self, forKeyPath: languageKeyPath)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard (object as? UserDefaults) === defaults,
keyPath == languageKeyPath,
let change = change
else { return }
if let updatedLanguage = change[.newKey] as? String {
print("updatedLanguage : \(updatedLanguage)")
}
}
}
// Test code, run init to observe changes
let viewModel = TopViewModel()
NSUserDefaults and KVO issues
I suggest making use of the appropriate notification: NSUserDefaultsDidChangeNotification.
Search for AppPrefs in the Apple Documentation within Xcode and it'll show an example app which does exactly what you want to do. Just compile and run! It makes use of the NSUserDefaultsDidChangeNotification.
This is the code being used to register an observer:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
ReactiveKit Bond KVO observe UserDefaults
It might not be obvious, but if the observed value can be nil, the ofType
argument must be an Optional type. In your case, that would be:
userDefaults.reactive
.keyPath(LocationManager.HomeLocationKey, ofType: Optional<String>.self, context: .immediateOnMain)
...
iOS NSUserDefaults Watching the change of values for a single key
Edit: viewDidUnload
is now deprecated, use dealloc
instead to removeObserver
This should work perfectly, I have just tested here.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSUserDefaults standardUserDefaults] addObserver:self
forKeyPath:@"SomeKey"
options:NSKeyValueObservingOptionNew
context:NULL];
// Testing...
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"test" forKey:@"SomeKey"];
[defaults synchronize];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"SomeKey"];
}
- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
if([keyPath isEqual:@"SomeKey"])
{
NSLog(@"SomeKey change: %@", change);
}
}
Things you could test.
Put a break point in viewDidUnload and make sure the view isn't disappearing on you (since you are changing
SomeKey
from another viewController) If this is the case then maybe move your register/de-register code into init/dealloc depending on how your VC works.Use explicit KeyPath like
@"SomeKey"
not a substitution likeSOME_NSSTRING_VARIABLE
Related Topics
Setting Up a Plist to Store Application Data (Not Settings) for an iPhone Game
Track Cellular Data Usage Using Swift
Simple Uitableview in Swift - Unexpectedly Found Nil
Uipickerview as Inputview of Uitextfield
Warning in Custom Map Annotations Iphone
Play a Video from Youtube in a Avplayerviewcontroller in Swift
Application Is "Ready to Sale" But Not Reflected on Itunes Store
iOS 10: How to Show Incoming Voip Call Notification When App Is in Background
Nsdateformatter Returns Nil in Swift and iOS Sdk 8.0
Running Nsurlsession Completion Handler on Main Thread
Why Is Audio Coming Up Garbled When Using Avassetreader with Audio Queue
Send Image Along with Other Parameters with Afnetworking
Getting Random "Facebookerrdomain Error 10000"
How to Remove All References for Outlet
Prepare(For Segue: Uistoryboardsegue, Sender: Anyobject) Missing in Swift 3.0/Xcode 8 B6