UserDefaults.didChangeNotification not firing

Hi,

I'm currently working on an app made originally for iOS 15. On it, I add an observer on viewDidLoad function of my ViewController to listen for changes on the UserDefault values for connection settings.

NotificationCenter.default.addObserver(self, selector: #selector(settingsChanged), name: UserDefaults.didChangeNotification, object: nil)

Said values can only be modified on the app's section from System Settings.

Thing is, up to iOS 17, the notification fired as expected, but starting from iOS 18, the notification doesn't seem to be sent by the OS.

Is there anything I should change in my observer, or any other technique to listen for the describe event?

Thanks in advance.

Answered by DTS Engineer in 805946022

Hmmm, this is tricky. The docs for this notification are pretty clear about its semantics:

This notification isn't posted when changes are made outside the current process, or when ubiquitous defaults change.

And Settings is definitely “outside the current process”.

Having said that, this is a pretty common case and it’s probably worth filing a bug about it. Please post your bug number, just for the record.

In the meantime, the solution is to use KVO to monitor for changes. Quoting the docs again:

You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

Hmmm, this is tricky. The docs for this notification are pretty clear about its semantics:

This notification isn't posted when changes are made outside the current process, or when ubiquitous defaults change.

And Settings is definitely “outside the current process”.

Having said that, this is a pretty common case and it’s probably worth filing a bug about it. Please post your bug number, just for the record.

In the meantime, the solution is to use KVO to monitor for changes. Quoting the docs again:

You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Can you post an example how to do this? Thx ;)

Find a solution:

userDefaults.addObserver(self, forKeyPath: "youKey", options: .new, context: nil)

And:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

    //do something 

}

If you’re using Swift then it has a much nicer interface to KVO, namely the observe(_:options:changeHandler:) method. For example, if I add code like this:

extension UserDefaults {
    @objc dynamic var myProperty: String? {
        get {
            self.string(forKey: "myProperty")
        }
        set {
            self.set(newValue, forKey: "myProperty")
        }
    }
}

to expose the defaults value as a property, I can then write code like this:

class MyClass {
    var myPropertyObservation: NSKeyValueObservation?

    func startObserving() {
        let defaults = UserDefaults.standard
        self.myPropertyObservation = defaults.observe(\.myProperty, options: [.new, .old]) { _, change in
            print(change)
        }
    }
}

I then modify the defaults value using defaults:

% defaults write "com.example.apple-samplecode.Test764675" "myProperty" "some value"

and the change handler runs and prints:

NSKeyValueObservedChange<Optional<String>>(kind: __C.NSKeyValueChange, newValue: Optional(Optional("some value")), oldValue: Optional(nil), indexes: nil, isPrior: false)

Nice!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

UserDefaults.didChangeNotification not firing
 
 
Q