Always show fresh content!

Always show fresh content!

Apple watch being used.
Watch apps should serve value in a glance

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
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.

WatchKit API documentation

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.

Sequence on events for a background refresh task.
The sequence of events in a background refresh

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.

    .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 to
handle(_ 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)

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 to
handle(_ 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)

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.

This Post Has 6 Comments

  1. Sascha

    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.

  2. Sascha

    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.

  3. Amos

    Any idea how to set background refresh in SwiftUI life cycle(No ExtensionDelegate at all!)

  4. Brainware

    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.

  5. Darcy

    I get an error after line 25: “Static member ‘url’ cannot be used on instance of type ‘BackgroundService'”

  6. Scott

    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.

Leave a Reply