Hi Andy,
In your WatchOS app extension's Signing and Capabilities section do you have a "Background Modes" section? I do. Additionally I have session type set to "None" and for Modes I have "Remote Notification" checked. I believe that is all you need.
What on earth is "BAR"?
The issue is that for me, background processing occurs only if the user has started my app and it now is in the application dock. If the app is dead, although it gets started by WatchOS at the desired time, it cannot process the data file until the user brings it to the foreground. I just want to know if that is the intended functionality or if I'm missing something.
Post
Replies
Boosts
Views
Activity
I believe that BAR stands for Background Application Refresh. Therefore the snippet you posted from WWDC is for scheduling one. I'm also pretty certain that you need to at least add background session type set to "None" to your app's signing and capabilities and that is what the error in the console is complaining about.
I have mine set to process remote notifications but it doesn't have to be set to anything, especially if you don't need to use an extended runtime session. Also the session type has a choice for "None" which represents a generic background refresh session that you want to create. Your app doesn't have to have an extended background runtime session, it just needs to declare that it uses a background refresh of a generic type ("None")
Why not give it a try? The worst that will happen is nothing
Hi Andy,
Very happy for you! So your first issue was because you were using SwiftUI, instead of a Storyboard? I'm glad you got it working. If you reboot your watch, does your app get started in the background and allowed to finish processing in the background?
Mine updates my complications well but only if I started the app, still has issues when launched by WatchOS.
If the watch was on the charger and locked when my app was woken up, I'd get a bunch of permission denied errors. This was due to various configuration files which I used that had the default complete file protection on, so my app couldn't access them if launched while the watch was locked. It still doesn't work in the most extreme case when on the charger, as I now get "Operation not permitted" in the urlSession(_ session: , task: , didCompleteWithError error: ) method when trying to initiate the download.
Finally I noticed a lot of issues because of various data I had stored in the keychain accessible only if the user set a passcode and the device had to be unlocked when the app was launched. If you use the keychain you may want to review your code.
I test the app on 2 watches simultaneously, one running WatchOS 7 and the other running WatchOS 6.2.8. I don't know if it's because of WatchOS 7 or because that watch is my regular Apple Watch, as opposed to the other one which is just for testing, but my WatchOS 7 watch schedules tasks in a much more timely fashion.
Hi Andy,
Regarding using SwiftUI for your app and the complications, maybe you missed something? In the WWDC'20 as you know there are videos demoing how to create complications using SwiftUI. Maybe that's only for >= WatchOS 7..
If your WatchOS app creates and writes to any files, the default is to have complete file protection, meaning those files will NOT be accessible for reading to your app if the screen is locked (regardless of whether it is on the charger or not).
So if your app is eventually started by WatchOS in the background, accessing any of those files will fail and throw an exception. The only files you can access when the screen is locked and your app is running in the background are either files you explicitly created with no file protection or the file(s) which WatchOS downloaded for you in the temp area via the background refresh download task.
When using background refresh either on WatchOS or iOS there's a tradeoff between usability and security. You most likely don't have to worry about that if the nature of your data doesn't require strong security, and from what you've shared, it looks like your app is fine.
My issues seem to stem from wanting to make a highly secure WatchOS app, but complications require security to be more lax than the default. I may have to change my app architecture and utilize complication push notifications instead of background refresh. We'll see.
4 years later and I have this issue as well. I take it this has not been solved?
Hi Andy,
Thanks for the suggestion, unfortunately I already have that. The one thing my app is doing is that when it transitions to the background it explicitly suspends timers and if started by WatchOS in the background my app does not fully initialize itself by design. I thought that was okay, especially since the complication update code is self contained and doesn't really need the app portion to initialize itself until it's in the foreground. I thought I was supposed to do that, but maybe I shouldn't?
It appears that when the app resumes the URLSession's download task, less than 50 milliseconds later, the request fails: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSErrorFailingURLStringKey=https:[my request url],
NSErrorFailingURLKey=https:[my request url],
_NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundDownloadTask <UUID1>.<1>",
"LocalDownloadTask <UUID2>.<1>"
), _NSURLErrorFailingURLSessiontaskErrorKey=BackgroundDownloadTask <UUID3>.<1>}
How do I determine the reason why the operation is not permitted when the app is launched in the background? This seems to be the reason why my complication can't update when started by WatchOS.
Anyone care to take a guess?
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
Are you behind a firewall that prevents notifications from reaching your watch and phone?
@developer555,
The NSURLSession task is scheduled when the system activates my app in the background and calls the handle(_:) method in my ExtensionDelegate. When the task type is WKApplicationRefreshBackgroundTask I take 2 actions: Schedule the next background refresh to occur in 15 minutes
Call a method which then creates a background URLSession configuration, and then creates a URLSession for my network request. After creating the session, I resume it just before leaving that method.
func handle(_ bkTasks: Set<WKRefreshBackgroundTask>) {
for aTask in bkTasks {
switch aTask {
case let bkTask as WKApplicationRefreshBackgroundTask:
scheduleNextAppBackgroundRefresh()
createAndResumeURLSession()
bkTask.setTaskCompletedWithSnapshot(false)
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
/* This adds the urlSessionTask to a list, which will be used to mark
		all tasks as completed upon successful download of data
		 */
handleDownload(urlSessionTask)
default:
// Ensure that unhandled tasks are marked as complete
aTask.setTaskCompletedWithSnapshot(false)
}
}
At some point in the future I may get back data from the NSURLSession. Note that the neither the background refresh nor the URLSession task scheduling is contingent upon getting data successfully. This is because there may be network issues such as timeouts which occur. Please note that this is all boilerplate code, and you should be able to find some examples on the net
Hope this helps. Let me know
Note: This has me thinking as to whether I need to take any actions such as mark tasks completed if an error occurs during the download. I suspect I need to.
@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
@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?
@developer555,
I also just added earliestBeginDate and the willBeginDelayedRequest delegate method, calling the completionHandler with .continueLoading. Here's what I observed:
The scheduled background refresh always schedules the background refresh task exactly when specified (every 15 minutes)
When it fires, a NSURLSession is created with a background configuration type
I set earliestBeginDate to now + a few seconds, since I already have an interval of 15 minutes for when I schedule the background refresh.
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!
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.
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.
I'm also having this issue with Xcode 12 (12A7209), when attempting to debug a WatchOS app on the simulator. Debugging an iPhone app works fine, and so does attaching to a running WatchOS app.
I take it this issue has not yet been resolved?
The issue must be uniquely related to my WatchOS app. Xcode 12 works fine with sample programs. For now I will debug by starting the WatchOS app manually and attaching to it. The shortcoming of this strategy is that all the debug statements no longer go to the Xcode console but rather to the OS X console's WatchOS simulator. It's the best I can do at the moment.
Thanks