Setting values in UserDefaults in DeviceActivityMonitor extension

I understand that the DeviceActivityMonitor extension is designed to be very lightweight, and the system terminates the extension as soon as its callback functions return.

However, I want to save values to UserDefaults inside the extension's callback functions. This creates concurrency issues for my app because the documentation for UserDefaults states that "When you set a default value, it’s changed synchronously within your process, and asynchronously to persistent storage and other processes."

In order to guarantee that these values are persisted before the extension terminates my app, I want to call UserDefaults.synchronize(), but its documentations states that it's "unnecessary and shouldn't be used." Furthermore, it's listed under "Legacy" but not marked deprecated.

Is synchronize() the recommended way to solve my concurrency problem? Or could there be a better way to wait for storage synchronization before returning from a synchronous function?

Replies

Is synchronize() the recommended way to solve my concurrency problem?

No. As the doc says, this function is a no-op.

What the other quote is trying to say is that, when you set a value, there’s no guarantee it’ll show up in other places before the set call has returned. That doesn’t mean that the user defaults system is allowed to drop the value, just that it might take a while to propagate.

What sort of thing are you saving in user defaults?

My experience is that a lot of folks reach for it for things for which it was never intended. User defaults, as the name suggests, is intended to for store preferences, not app data.

Share and Enjoy

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

  • I'm storing timestamps of certain screen time thresholds with DeviceActivityMonitor. Whether or not this is its intended use, something is still off. DeviceActivityMonitor is part of an app extension that is invoked and terminated by the system. Once its functions return, the extension is immediately terminated, meaning there may be data in UserDefaults that never gets persisted. The also docs don't say it's a no-op just that it's "unnecessary" - but maybe it's necessary inside an app extension?

Add a Comment

I'm storing timestamps of certain screen time thresholds with DeviceActivityMonitor. Whether or not this is its intended use, something is still off. DeviceActivityMonitor is part of an app extension that is invoked and terminated by the system. Once its functions return, the extension is immediately terminated, meaning there may be data in UserDefaults that never gets persisted. The also docs don't say it's a no-op just that it's "unnecessary" - but maybe it's necessary inside an app extension?

Once its functions return, the extension is immediately terminated, meaning there may be data in UserDefaults that never gets persisted.

As Quinn said, the data isn't dropped. When you insert or update data in UserDefaults, it synchronously** leaves your process and goes elsewhere in the system. Even if your process immediately quits or crashes, the data that's "elsewhere" isn't lost.

However, it sounds like you're finding that the UserDefaults data is not being propagated back to your app. That's the intended behavior. For privacy reasons, device activity extensions are not allowed to pass sensitive user information (such as what apps users are using) out of the extension processes, and you can't circumvent this restriction via UserDefaults.


** That's why synchronize() no longer does anything. By the time you'd have a chance to call it, the data is already synchronized.

  • Thanks for the clarification! Is there a doc that says that? The docs I linked state that the data is persisted asynchronously. Also, the DeviceActivityMonitor extension is allowed to access UserDefaults, since it doesn't have access to granular screen time data like specific apps. It's the DeviceActivityReport extension that has these restrictions. Defaults set in the monitor extension are usually propagated to my app, but sometimes not, which led me to think it was a concurrency issue.

Add a Comment

@charleswinston Wondering if you were able to figure out how to use UserDefaults for your use case? I'm using the DeviceActivityReportExtension framework and was able to get UserDefaults working when testing on simulator, but when I test on an actual device, it doesn't work

I ran this past DTS’s Device Activity specialist and they confirmed that it’s expected behaviour. Your DAR extension is running in a sandbox that was specifically designed to prevent information ‘leaking’ from it, and so it can’t write to shared user defaults, use the network, and so on.

Like many sandbox restrictions this isn’t enforced on the simulator, which is why you’re seeing different behaviour there.

Share and Enjoy

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