watchOS: Background URL session always takes over an hour to return

I’m trying to get background URL sessions working to keep my complications up to date. The docs and session videos from this year state that I should get ~4 background network updates per hour if I have my complication active, but it is taking up to 1.5 hours to get just one background URL session to return on watchOS 7 beta 4.

I’m using the sample code from this year’s WWDC session on keeping complications up to date.

I am scheduling the URL session to begin after 15 minutes. I have a complication active on the watch. The watch has plenty of battery and is connected to wifi. I’m completing all URL background refresh tasks within 15 secs.
  • Important Note*

I do get a URLSessionRefreshBackgroundTask around the time of the earliest begin date, but it just calls willBeginDelayedRequest then urlSessionDidFinishEvents - the download itself doesn’t happen for another hour. (I call setTaskCompleted once urlSessionDidFinishEvents is called, as the session video recommends.)

Is anyone else experiencing long delays between when a scheduled download is supposed to begin and when the app is actually woken up?

I've filed this as FB8386427 in case any engineers happen to see this.
  • Were you ever able to resolve this? I'm facing the exact same challenge now after days of debugging and reading every resource available. If you or anyone else got it working, I'd GREATLY appreciate a quick sample project that I can compare against as I'm really banging my head against a wall here trying to figure out why it's not working reliably. Thank you!

Add a Comment

Replies

Hi Developer555,
I have 2 watches, a personal one (Series 5) and a development one (Series 4).

On my wrist, the personal watch gets data approximately every 15 minutes. Sometimes it skips for an hour. When the watch is charging I get no updates at all and my app is killed. Perhaps my app uses too much memory and that's why it's killed when the watch is charging? Not sure.

I can configure my app to download data for complication refresh every 30 minutes, 45 minutes, an hour or 2 hours.

It works well, provided that my app is suspended in the background. If the app is killed for whatever reason, even though WatchOS restarts my app and it schedules a refresh, I don't get URLSession download result until I make the app go into the foreground. Haven't gotten an answer as to whether that is intended functionality or if I need to do something.

I should point out that the amount of data I get is always less than 500 bytes.

On the development watch, I don't get the updates in as frequent a manner, but it may be because I use it only for development. I've started to wear it as well to see if that improves the refresh frequency.

So to summarize:
  • Check the size of the download data

  • Check whether your app is suspended in the background when a refresh is triggered

  • Watch out for any files which can only be written to if the watch is not locked. WatchOS schedules your task based on how much work it does and how often your app is used

If your app has more than one complication type I'd add it to as many watch faces as possible, to see if that helps you detect the issue.

Question for you, does it work fine on the simulator? I'd try that first with Xcode attached to your app. On the simulator when connected to Xcode, refreshes will be done in a very timely fashion and there's no budget to worry about.

All the best,
  • Sal


I thought I had finally gotten this working because my development watch was regularly updating every 20 minutes. But now that I've updated my personal watch to watchOS 7, I'm seeing the exact same behavior of it sometimes taking over an hour for a background URL session to return.

And at least as of a beta or two ago, the simulator wasn't giving me background URL refreshes at all. I'll have to give that a try again.

Would you be able to post your networking code as well as your background refresh code, by any chance? I'd like to compare against my own to see if there's something I'm missing here.
Specifically, when do you schedule the next URLSession task after the previous one is completed?

Apple has previously posted guidance saying that URLSession penalizes apps for repeated launching and requires at least 10 minutes between tasks. Does that mean you have to wait 10+ minutes before you can even *schedule* the next URLSession? I started waiting until the next background app refresh to schedule the next URLSession task and saw some improvement, but that also introduces a big delay since sometimes the next background app refresh doesn't trigger for 15+ minutes.

The sample code shown in one of this year's WWDC videos had the next URLSession getting scheduled right when the previous one completed, but other Apple docs and my testing seems to indicate that's not correct.
@developer555,
I do not schedule a URLSessionTask at all. I only schedule a background refresh. I create the URLSession background task uses the default settings. I do not use earliestBeginDate nor do I have the willBeginDelayedRequest delegate method implemented. Perhaps you're doing more than you need? Do a search on always show fresh content on WatchOS. There's a pretty good tutorial that comes up, that illustrates the bare minimum you need to get it going. You may want to compare your work with the tutorial. You're probably missing a very small amount of code.

Let me know how it turns out


Apple actually recommended using earliestBeginDate to schedule URLSessions in this year's WWDC videos on complications. I had had the same issues with just immediately launching URLSessions last year, so I was trying this new method to see if it worked any better. I'll test out the old method again to see if the performance is any better - and I'll check out the blog post you recommended as well.
@developer555,
Yes, please check out that blog and let me know. On the topic of complication updates, would you know if you're penalized for updating a complication while your app is in the foreground? Or is it only when updating in the background?

My app's data model gets updated every 2 seconds when in the foreground and I update the complication then as well.

I know that if you're updating a complication via the transferUserInfo for complications there's a budget, but what about when you're just updating it from the WatchOS app?
I believe it's not budgeted when you're in the foreground, though you could always space out the updates so it's only updating the complication on every, say, 10th update in the foreground.
@developer555,
I also just added earliestBeginDate and the willBeginDelayedRequest delegate method, calling the completionHandler with .continueLoading. Here's what I observed:
  1. The scheduled background refresh always schedules the background refresh task exactly when specified (every 15 minutes)

  2. When it fires, a NSURLSession is created with a background configuration type

  3. I set earliestBeginDate to now + a few seconds, since I already have an interval of 15 minutes for when I schedule the background refresh.

  4. On a simulator, if the debugger is attached everything works fine. As soon as I detach the debugger, the background refresh task is still scheduled appropriately, but the URL session is no longer started!

  5. On a real device, I sometimes get a NSURLSession executed in the background at a 15 minute interval, but most of the time only the application refresh task is triggered.

  6. The spacing between each NSURLSession creation is 15 minutes and30 seconds.

Is it possible that this is just the way that it works? When updating a complication in the background from a WatchOS app, perhaps I'm exceeding the 96 complication updates that I'm trying to do in a 24 hour period? I've added code that keeps track of every complication update, both when done in the background and foreground. Not sure when the 24 hour period starts, so hopefully this'll shed some light on my issue.

But then again, this is an issue with NSURLSession and not the complication update. I'm starting to think that the NSURLSession being kicked off is a function of how many apps are on the watch, and how many are scheduling background refreshes.

Were you ever able to resolve this? I'm facing the exact same challenge now after days of debugging and reading every resource available. If you or anyone else got it working, I'd GREATLY appreciate a quick sample project that I can compare against as I'm really banging my head against a wall here trying to figure out why it's not working reliably. Thank you!