Introduction
With the upcoming watchOS 3 update to all Apple Watch devices, the performance of many watch applications is going to significantly improve. This is mainly due to the new forms of background execution which watchOS apps can take advantage of in order to refresh their content periodically and always have the latest information ready to be viewed by the user.
In this tutorial I am going to show you how you can utilise the new WatchKit APIs in watchOS 3 to have your own application use these new background tasks. It is important to note that, even if your app is set up properly to support these background refreshes, depending on a particular user's configuration, the system may not let your app run in the background.
This tutorial requires that you are running Xcode 8 on OS X El Capitan or later.
1. Background Tasks
The first important new class in watchOS 3 is the WKRefreshBackgroundTask
class. This is an abstract class which you should not subclass or create instances of yourself. It defines only one property, userInfo
, which can store extra information relating to the background task. It also defines a single method, setTaskCompleted()
, which you must call yourself to tell the system that the task is complete. If you do not call this then the system will spend the maximum amount of time budgeted to your app trying to complete the task, which in turn wastes battery and hurts the performance of other apps.
When your application should handle a background task being completed, the system will call your watchOS extension delegate's handle(_:)
method. This method's single parameter is a set of WKRefreshBackgroundTask
objects which you need to loop through and process accordingly.
Within the WatchKit framework, Apple provides the following four concrete subclasses; watchOS will pass your extension delegate an instance of the appropriate class depending on the task.
-
WKApplicationRefreshBackgroundTask
is a general background task type that is not specialised for a particular refresh type. The most common use case for this type is for scheduling other tasks. For example, you could schedule a background refresh for your app at a specific time of day each day. When the correspondingWKApplicationRefreshBackgroundTask
object is then passed into your extension delegate'shandle(_:)
method, you could create a background network task to download some data to refresh your app's content. -
WKSnapshotRefreshBackgroundTask
is a task type specifically for when your app needs to update its snapshot. We will discuss snapshots later on in this tutorial. All you need to know for now is that an app's snapshot is used as its launch image and can also be shown in the user's dock (accessible by pressing the side button). Please note that this type of background task has a unique task completion method which we will also discuss later on. -
WKWatchConnectivityRefreshBackgroundTask
is a task type for when you have transferred data from your iPhone to your watch via the WatchConnectivity framework. As this framework provides a method to transfer complication data directly to the watch, this corresponding background task type is mainly used to schedule snapshot updates in response to data being received from the iPhone. -
WKURLSessionRefreshBackgroundTask
is a task type given to your extension delegate when a background networking task has completed. In this scenario, the delegate methods you set up for theURLSession
object are still called, and it is here where you need to work with the data appropriately. Thehandle(_:)
method of your extension delegate is only called so that your application can respond to the network operation being completed. TheWKURLSessionRefreshBackgroundTask
does not give you access to theURLSession
object nor the data that was transferred. The only extra property it defines is a string calledsessionIdentifier
which you can use to determine what network process has been completed.
Scheduling a Refresh
There are a number of ways that you can schedule a background refresh in watchOS 3. For a simple application or snapshot refresh respectively, you can use the following two methods on any WKExtension
object.
scheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:)
When completed, this method will return a WKApplicationRefreshBackgroundTask
object to your extension delegate. The watchOS system will attempt to wake your extension in the background at the time specified by the Date
parameter.
Please note that it is not guaranteed that your application will be woken in the background at this precise time. Due to other circumstances (such as battery remaining, other apps running and in memory, etc.) the system may decide to wake your app at a later time.
The userInfo
parameter will be accessible via any of the background task objects passed to your extension delegate. This can be particularly useful if you need to send information relating only to the current refresh. Lastly, the scheduledCompletion
parameter can provide a code block which will be run once your refresh has been scheduled or has failed due to some error.
scheduleSnapshotRefresh(withPreferredDate:userInfo:scheduledCompletion:)
This method will return a WKSnapshotRefreshBackgroundTask
object to your extension delegate. The parameters for this method are identical to when scheduling a regular application refresh. The only difference with this method is the type of refresh that you are scheduling.
The following is an example piece of code which could be used anywhere in your watchOS application to schedule a background refresh:
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: Date(timeIntervalSinceNow: 60 * 60), userInfo: nil) { (error: Error?) in if let error = error { print("Error occurred while scheduling background refresh: \(error.localizedDescription)") } }
Creating Other Background Tasks
As mentioned earlier, a WKWatchConnectivityRefreshBackgroundTask
is created in response to you sending data to the watch from its paired iPhone via the WatchConnectivity APIs.
The last type of background task, WKURLSessionRefreshBackgroundTask
, is scheduled automatically by the system when you create a URLSession
object with a background configuration. Doing this from anywhere within your watch app will cause the system to automatically schedule the request for you and call your extension delegate's handle(_:)
method when completed. The following example code contains two functions within an example class. The first function configures and starts a background download task. The second is a delegate method declared in the URLSessionDelegate
protocol.
class SomeClass: URLSessionDelegate { func beginDownloadTask() { let config = URLSessionConfiguration.background(withIdentifier: "exampleSessionIdentifier") let session = URLSession(configuration: config, delegate: self, delegateQueue: nil) let task = session.downloadTask(with: URL(string: "http://www.example.com/data")!) task.resume() } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { print("Session did complete") } }
2. Watch App Snapshots
In watchOS 3, app glances have been removed entirely from the Apple Watch and instead have been replaced by the dock. This dock is accessed by pressing the side button on the watch and contains any number of apps which the user can configure:
If your app has been placed in a user's dock then you are responsible for keeping the visible "snapshot" shown in the dock up to date.
Your app is notified when you need to update your snapshot by a WKSnapshotBackgroundRefreshTask
being passed into your extension delegate's handle(_:)
method. When you receive this notification, in addition to your WatchKit extension being woken in the background, your app's root interface controller is also woken in the background and off screen. In this process, the following methods are called in this order:
- The root interface controller's
awake(withContext:)
method - The root interface controller's
willActivate()
method - The extension delegate's
handle(_:)
method
At any point in these three methods, you can customise the appearance of your app's interface to create the snapshot shown in the dock.
The final step of refreshing the snapshot happens in the extension delegate's handle(_:)
method. As with the other background task types, you must tell the system that the task has been completed. For snapshot refreshes, however, this is done by calling the setTaskCompleted(restoredDefaultState:estimatedSnapshotExpiration:userInfo:)
method on the WKSnapshotBackgroundRefreshTask
object. As soon as you call this method, watchOS captures the snapshot of your app's current interface and keeps this in memory until the next refresh. To better understand this method, let's go through all of its parameters:
-
restoredDefaultState
is just a boolean value specifying whether or not your snapshot is of the first interface controller of your app. If you have transitioned to another interface then pass infalse
for this value. -
estimatedSnapshotExpiration
is aDate
object which specifies when the system should next try to refresh your app's snapshot. If you do not need to schedule another refresh immediately then you can pass in the newDate.distantFuture
class property. -
userInfo
is a collection of custom data and will be passed into the nextWKSnapshotBackgroundRefreshTask
object if you are scheduling another refresh straight away.
3. Improvements to WatchConnectivity
The WatchConnectivity framework introduced in watchOS 2 and iOS 9 is also gaining some new functionality with watchOS 3 and iOS 10.
If a user has put your app's complication on their watch face, then you can transfer data from the phone to the watch up to 50 times in a single day. This is done, as in iOS 9 and watchOS 2, via the WCSession
transferCurrentComplication(userInfo:)
instance method. New in watchOS 3 and iOS 10 is the WCSession
remainingComplicationUserInfoTransfers
property, which will tell you how many transfers are remaining for that day.
Another new WCSession
property added with this year's platform updates is the hasContentPending
property, which is just a boolean value signifying whether or not there is data yet to be transferred between the iPhone and Apple Watch.
If you want to learn more about WatchConnectivity and how it works, you can check out our previous tutorials covering the framework.
Conclusion
Overall, the new APIs in watchOS 3 allow you to quickly and efficiently refresh your Apple Watch app's content in the background. As more apps adopt these new APIs, the performance of watchOS apps in general will greatly improve and create a much better user experience.
The linked GitHub repo contains a watchOS 3 Xcode project with some example code showing how you can schedule and respond to background refreshes in your own apps.
As always, please be sure to leave your comments and feedback in the comments below.
Comments