
Background refresh on watchOS
Apps on a watch have a unique challenge, attention span. Typically the user spends less than five seconds of attention before the wrist is lowered and the session is over. This emphasizes the importance of having relevant and up to date information available.
If you can anticipate when your relevant data will update you can schedule a background refresh task to fetch data just in time so that your app always shows relevant information.
Background tasks are performed by the operating system on your app’s behalf, and it’s on a budget. The scarce resource is battery life. If your app is in the dock (either most recent apps or user-selected) you can expect to perform one refresh per hour. However, if the app also has complication on the active watch face this budget is increased to cover a few background updates per hour. This is intentionally kind of vague, with no promises.
Budget Your App’s Background Time
WatchKit API documentation
The delivery of background tasks and the allocation of background execution time are completely up to the system, and there is no public API to change the behavior. Understanding the limits helps you better design your app’s background update strategy.
The sequence
The Process of background app refresh is a multi-step process. It can look intimidating but don’t worry, I will guide you through it step by step.

The first step – Schedule the task
To kick things off, you will need to schedule a background refresh at an appropriate future date.
Unless you received an error in the completion block, your background task is now scheduled to your preferred date.
WKExtension.shared()
.scheduleBackgroundRefresh(withPreferredDate: date,
userInfo: nil,
scheduledCompletion: completion)
The second step – Handle the Refresh task
When the system decides to execute your background task. Your app will be launched into background mode and your ExtensionDelegate receives a call tohandle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)
With a task of type WKApplicationRefreshBackgroundTask
.
In ExtensionDelegate.swift:
The ExtensionDelegate calls updateContent
in my singleton class BackgroundService
(full version at the bottom) to perform the task and then sets the task completed. Let’s look at the function updateContent.
Create a Background URLSession with an identifier. Then create a downloadTask and resume it. Now the download will be performed for you and the data will be stored in a temporary file.
func updateContent() {
let configuration = URLSessionConfiguration
.background(withIdentifier: "myBgTaskID")
let session = URLSession(configuration: configuration,
delegate: self, delegateQueue: nil)
let backgroundTask = session.downloadTask(with: url)
backgroundTask.resume()
}
Third step – Download
This step is actually handled by the system and it’s performed on a separate process, completely isolated from the app.
Great! 👍
The fourth step – Handle URL Session Task
As the download is about to finish your app will be launched into background mode again and the ExtensionDelegate will receive another call tohandle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)
but this time with a WKURLSessionRefreshBackgroundTask
Again the task is referred to BackgroundService
but an important difference. The task is not set as completed, instead, it is passed as an argument. service to hold on to. Let’s have a look.
func handleDownload(_ backgroundTask:
WKURLSessionRefreshBackgroundTask) {
let configuration = URLSessionConfiguration
.background(withIdentifier: backgroundTask.sessionIdentifier)
let _ = URLSession(configuration: configuration,
delegate: self, delegateQueue: nil)
pendingBackgroundTasks.append(backgroundTask)
}
Some strange things are going on here! 🤷♂️ A URLSession is created but it’s not used. 😮 Then the task is stored to an array of pending tasks to be called later when the download is finished.
The purpose of this is to re-connect the URLSessionDelegate to the download task handled by the system. The session is created with the identifier from the background task. By creating a session with a delegate you can now receive the delegate callback when the corresponding download finishes.
The final step – Handle the file
Now that the delegate is connected to the background URLSession it will receive a callback urlSession didFinishDownloadingTo location
and all that is left to do is to handle the file. Either Store the file as-is to a permanent location or process the file by decoding the data as JSON as in my case. 🏁
The background refresh is done, this is an excellent place to schedule the next refresh if you aim to have periodic updates.
Hope this helps you, I was really struggling to read between the lines of the documentation and I decided to clarify it a bit.
Here is the complete implementation of the BackgroundService
for your reference.
Thank you for the article. I was handling background routine in a wrong way. By the way, you are not cleaning background task upon finish.
Thank you for the article. I was handling background routine in a wrong way. By the way, you are not cleaning background task upon finish.
Any idea how to set background refresh in SwiftUI life cycle(No ExtensionDelegate at all!)
But, WHEN should a background task be scheduled? I have a complication that needs updating every hour. But, I don’t know when I should tell WatchOS to schedule this update.
I get an error after line 25: “Static member ‘url’ cannot be used on instance of type ‘BackgroundService'”
Does it have to be implemented via the delegate? I’d rather, for example, use the downloadTask(with:completionHandler:) and just deal with things in the completion handler, as opposed to using the older style delegate methods.