Making Network Requests from DeviceActivityMonitor Extension

I am having a lot of trouble reliably making network requests from the DeviceActivityMonitor extension.

In the app extension, when intervalDidEnd is called by the system, I add some data to the app's local CoreData store and sync that with the server. I use URLSession dataTask to make a POST request to my server, with URLSessionConfiguration.sharedContainerIdentifier set to my App Group's shared identifier. It is extremely rare that my server receives this request, although sometimes (rarely) it does.

I've tried things like wrapping the task in performExpiringActivity or using URLSessionConfiguration.background suspecting that this task needs to run in the background, but none of it seems to work.

What I suspect is happening is that the App Extension gets cancelled before the request is able to be invoked. Looking at the console logs, sometimes the work stops before the request is called, sometimes during the request task, and sometimes even before I am able to update my local data store.

It's been very difficult to find any information on the lifecycle of the DeviceActivityMonitor Extension. What I want to know is:

  • How can I reliably make network requests from the DeviceActivityMonitor Extension?
  • What is the lifecycle of this extension? (How long do I have to complete work inside callback functions invoked by the system? And can I extend this time if necessary?)
Accepted Answer

DeviceActivityMonitor extensions are intended to be very lightweight and their lifecycle ends as soon as the synchronous methods you override return. In other words, your extension will likely exit before a long-running asynchronous background task (like a network request) has the chance to finish. Please file an enhancement request to add support for asynchronous callbacks. As a workaround, you can try using a locking mechanism or a DispatchGroup to force the system to wait on your network request:

let waitForMyNetworkRequest = DispatchGroup()
waitForMyNetworkRequest.enter()
performMyAsyncNetworkRequest {
    // Talk to your server.
    // ...
    // Once you are done, signal the dispatch group.
    waitForMyNetworkRequest.leave()
}
// Force the system to wait 3 seconds for your network request.
waitForMyNetworkRequest.wait(timeout: 3.0)

Thank you for the solution, and it almost works perfectly! However, I am trying to wait for two network requests, and only the first one is running. Here is a sample of my code:

let waitForMyNetworkRequest = DispatchGroup()
waitForMyNetworkRequest.enter()
Task {
    // This request successfully hits my server
    await pushScoreUpdate()

    // These log statements and requests don't run
    logger.log("Successfully pushed score update")
    await pushHistory(since: since)
    logger.log("Successfully pushed history")
    waitForMyNetworkRequest.leave()
}
// Force the system to wait for your network request.
waitForMyNetworkRequest.wait(timeout: .now() + 60.0)

Also, I'm not even able to read the response from my first request, even though I've confirmed that it hits my server. Here's a sample of my networking code:

...
let configuration = URLSessionConfiguration.default
configuration.sharedContainerIdentifier = "group.core.data.Present"
configuration.isDiscretionary = false
       
let (data, _) = try await URLSession(configuration: configuration).data(for: request)
logger.log("Got response")
...

Maybe I'm not setting up the DispatchGroup right to wait for multiple async functions? Or maybe it's the way I'm configuring my URLSession that automatically exits the dispatch group? It appears that I've made progress in being able to send the initial request, but the extension is still terminating before I'm able to read the response.

Making Network Requests from DeviceActivityMonitor Extension
 
 
Q