Keeping Your Watch App Up to Date

Session 218 WWDC 2016

Keeping your Apple Watch app up to date is key to creating a great experience on watchOS 3. Learn how to use the new background tasks to keep your complications and the new watchOS 3 Dock snapshots consistent with your app's underlying model. Discover how to get the most from NSURLSessions and Watch Connectivity to keep data fresh and glanceable.

[ Music ]

[ Applause ]


Hello, and welcome to our session on keeping your Watch App up to date.

My name is Eric Lanz, and with me today is Austen Green.

We're both engineers on the watchOS team.

Here you see a screenshot from our calendar app.

When you hear the word app, this is probably what comes to mind, but watchOS has many ways to interact beyond the standard application.

For example, your users think of your notifications as an important part of your app.

If you have a complication, your users think of that as an important part of your app, too.

In watchOS 3, we're introducing the new application dock.

This feature allows users to add up to ten of their favorite applications to an always accessible dock.

They can then swipe through the dock to get a quick look at their data in one place.

This is now also part of your app.

People are using your application's data in many different ways and expect all of those ways to be in sync and up to date all the time.

This may sound like an impossible task, but don't worry.

We're here to help.

Today we'll be talking about five topics.

First, an overview of this API and how it works on watchOS.

Next, a walkthrough of an example application with some real code.

Then Austen will come on stage and show you how scheduling works behind the scenes.

After that, we'll share some best practices for adopting this API in your own applications.

To close out the session, we'll go through a case study of how we adopted this API in our own stocks application.

Let's get started by thinking about how we use our phones and our watches in different ways throughout the day.

In the morning, you can start your day waiting in line for coffee.

You can then browse the news on your phone for a few minutes.

Before you leave the shop, you check the weather with the complication on your watch face, a two-second task.

For lunch, you use maps again to find a great restaurant.

After a few minutes, you've made your choice and put your phone away.

On the way to the restaurant, you get a notification.

A quick glance at your wrist lets you know your friends will be a few minutes late, another two-second task.

At the end of the day, you use maps again to plan your route, maybe a detour.

You get an iMessage asking when you'll be home that night.

Use the quick reply features of watchOS 3 to send back, "On my way," another two-second task.

It simply is impossible to have our data ready in the couple of seconds that users are going to give us.

We need more time, and background refresh is the way to get it.

This powerful new API allows you to schedule runtime so you can have your data ready before the user needs it.

To understand what this new API can do, let's take a closer look at checking the weather on watchOS.

Here we see the foreground activity of looking at the weather complication.

Our data would have to be ready before this happened.

So let's use this API to schedule some time in advance to update our UI, but how can we update our UI without data.

We'll need more time to get the latest weather data from our server.

So let's schedule a task for that.

But how did we even get started on this chain of events?

We're going to need a way for the system to wake our application in the background.

On watchOS, the system wakes your application by giving it a task.

The system has a limited number of these available.

So make the best use of each one you get.

When the system wants to wake your application, it delivers one or more of these task objects.

Make sure to hold on to this task until you're finished processing your data.

The system delivers tasks by calling the new handled background task method on WK extension delegate.

We'll go through an example of this method later on today.

When you're finished with your background work, return the task to the system by completing it.

This is the fundamental process by which you obtain background runtime on watchOS.

Now that we understand the task system from a high level, let's dive in and look at the specific types of tasks that the system can create for us.

First, this is the application task.

This is a generic runtime task that you can schedule to have your application woken at a future date.

Within the application task runtime, you can do any kind of local processing.

You may want to update your complication timeline or download some data from your server with URLSession.

The URLSession task is how you find out that your data has finished downloading and ready to process.

Since watchOS is a shared ecosystem, it doesn't make sense to leave our application running while the data downloads.

It would be better to allow our application to sleep, and let the system do that for us.

Snapshots are a very important part of watchOS 3.

They are both your launch image and your preview image while running in the dock.

If the user settles on your app, it will start running live again.

So it's critical that your snapshot be up to date at all times.

The snapshot task is how you get runtime to prepare your UI in the background to be ready for the new snapshot.

When you complete this task, the system will automatically snapshot your UI.

Remember to always schedule one of these after you finish processing data, or the user won't see the work you just did.

When a notification arrives, your user will see it.

They will then expect your complication and your snapshot to be updated to reflect this data.

Users love applications that feel like a consistent part of the OS and are more likely to put those apps in the dock.

If the user does not interact with your application for more than one hour, the system is going to give you an opportunity to restore your default state.

Default state means different things to different apps, and some apps have no concept of a default state.

Designing great snapshots is a huge and important topic on watchOS 3.

We recommend you check out this other session for some great advice on designing excellent snapshots for your users.

The last task type is for watch connectivity.

On watchOS 3, we've integrated watch connectivity with our background refresh API.

This means you can now use watch connectivity messaging to get data to your application while it is running in the background.

Complication push, application context, sending a file, or sending user info will all wake your application in the background.

We hope that this edition will lead to even richer watch experiences for our users.

When your application is woken via watch connectivity tasks, use the standard API to get your data.

First, make sure the session is active.

Once the session reactivates, start monitoring the new hasContentPending property.

As long as this property is true, you still have data to process.

Make sure to hold onto the task until you're finished processing this data.

It is your responsibility to return the task to the system by completing it.

If you don't do this, you will exhaust your background runtime, and we're going to give you a crash report.

Austen will talk more in the second half of this session about these runtime caveats.

Let's quickly review the workflow of using our new background refresh API.

First, schedule a task.

Next, receive the task from the system.

Now you can do your background work.

Make sure to hold onto the task until you're finished doing this work.

You may use this runtime to schedule further work such as fetching data from your server with URLSession.

When finished, return the task to the system by completing it.

Before we continue, I want to stress an important topic, being a good citizen.

watchOS is a shared ecosystem, and there are many applications and system processes competing for CPU time and battery life.

It is our responsibility as developers in this ecosystem to do our best to use these resources efficiently.

Let's pretend the user launches your app at 3:00 p.m. You want to make sure you have a chance to check in with your server in an hour.

So schedule a task for 4:00 p.m. Well, what happens if the user launches your app at 3:50?

We could update our data now and again at four when the task runs, but that does not sound optimal to me.

A better approach is to use the runtime at 3:50 to reschedule our background task for an hour later, 4:50.

Every app is different, but hopefully you can find a pattern like this to help us maximize resources.

Okay, let's start looking at some code.

To help frame our sample code, we're going to walk through the timeline of an example application and show how you can write the code that corresponds to all of these life cycle events.

Let's take a look at a football app, and pretend that there's a big game tonight from 7 to 9:00 p.m. We know our user's favorite team is playing, and we expect them to be checking the score frequently.

Let's settle on a thirty-minute cadence for our background activity.

The background refresh API allows only one task of each type to be in flight at any given time.

So to start off, let's schedule our first task for 7:30.

At 7:30, we'll use that runtime to schedule the next task for 8.

At 8, we'll again use the runtime to schedule our next event for 8:30.

It is important to always make sure you have a future task scheduled, or you won't know when you'll next get a chance to run.

Here we see the code for scheduling an application task on watchOS.

First, let's set the fire date to thirty minutes from now.

Use the userInfo object to store some data about why you made this request.

In this example, I've put the date at which I made the request and a reason string that I can check later when the task comes back.

This property is optional, and any secure coding compliant data can be stored here.

This completion blog is how you find out that the system has successfully scheduled your task.

Note that just because the error is nil here, it does not mean the task will run at exactly the requested time.

Austen will talk more about when and why the system triggers certain tasks.

With our application task scheduled, let's zoom in on our timeline and look at only a five-minute window in which our task is scheduled to run.

When the system wakes us, our priority is to get the latest score data from our server.

So let's take a look at the code for starting a background URLSession on watchOS.

First, create a URLSession configuration object.

It is important that this object be configured as a background session because we're running in the background.

Also, set an identifier that we'll use later to access our data.

Next, create a URL session using this configuration.

We ask the URLSession to give us one or more download task objects where we can associate as many download tasks as we want with the session.

Keep in mind the system will only wake us when all associated tasks have finished.

Don't forget to call resume to start downloading the data.

Getting back to our timeline, we've got the URL download in progress.

So it is safe to complete our task and allow the application to sleep.

The system will continue downloading the data while we are suspended.

When our data is ready, URLSession will create a task and wake our application back up.

But what does wake our application really mean?

In concrete terms, waking your application means calling the new handle background task method on WKExtensionDelegate.

In addition to this call, we will receive a will activate call on our visible view controllers.

The system coalesces tasks and delivers them to us as a set.

We need to process all of the tasks in this set.

So let's get started by looping through them.

For each task, we can use an inline task to get an object of the types we care about.

In this case, we're processing a URLSession task.

So we need to rejoin the session using the identifier associated with the task.

URLSession is a highly asynchronous API.

We need to be careful to hold on to our tasks until we're finished processing this data.

We recommend you store the task in a collection and then drain the collection when finished, completing each task to return it to the system.

Make sure to complete task types that you don't specifically handle.

Remember, the system has a limited number available.

So complete each one that you get.

We've got our data.

Let's update our model.

You might consider updating your UI as well at this point, but we recommend you make use of this snapshot task runtime for that type of work.

So before we complete our URL task, let's make sure to schedule a snapshot.

With our snapshot scheduled, it's safe to complete the URL task and allow the application to sleep again.

Soon, the system will wake us back up with the snapshot task we just scheduled.

Now is our chance to update our UI, and get everything ready for the new snapshot.

Snapshots have a unique completion handler.

So let's take a look at the completion handler for snapshots on watchOS.

Every application must have a snapshot at all times.

This is because your snapshot is both your launch image and your preview image while running in the dock.

That rule means that when we complete a snapshot, we need to tell the system how long it is valid for.

Think about your data and how long it will be relevant to your users.

In this case, we have another event scheduled at eight.

So let's set our expiration for thirty minutes from now.

User info can optionally store some information about why we made this request.

That data will return to us with our next snapshot task.

WatchOS will give your application an opportunity to restore its default state after one hour of inactivity.

You can tell the system to skip that event by setting the restoredDefaultState property to true.

Doing this lets the system know that you are already at your default state, and don't need an extra task for that.

Apps that have no concept of a default state should consider always setting this property to true.

Once you complete a snapshot task, the system will suspend your application.

Your UI will then be automatically captured and used as your new launch image.

This activity will not wake your application.

We've made it through an end-to-end example of a common background refresh pattern and the associated code.

In case you didn't notice, even though we were looking at a whole five minutes on our timeline, our application was only active for 15 total seconds.

By chaining tasks, we were able to maximize our use of system resources.

You now have a good understanding of what this new API is, why you should adopt it, and how you can go about adopting it.

I'd like to welcome Austen Green to the stage to give you some deeper insights into how scheduling works behind the scenes.

Good luck [applause].

Hi, everyone.

I'm Austen Green.

I'm a watchOS engineer.

This morning, I would like to share with you some details about how scheduling works behind the scenes.

I'd like to provide some best practices that we picked up as we adopted background refresh in our own applications.

And, finally, I'd like to close with a quick case study about specifically how we adopted background refresh in our stocks application.

So let's get started.

So the first thing I want to talk about is runtime.

So while your application is in the foreground, you're always scheduled to run.

This means that your code gets to execute so that you can do things like update your model and draw your UI, and any other kinds of tasks that your application may need to do.

Now when your application moves into the background the system will typically suspend your application.

This means that your application doesn't get a chance to execute any code at all.

Now sometimes while your application is in the background, the system may want your application to perform a very specific task.

The system will wake your application and ask you to perform a specific task that it may want you to do.

In watchOS 2, there were several ways that the system could wake your application.

For example, to handle a long look notification, or perhaps ClockKit would ask your application to update its complication.

In watchOS 3, we're adding even more ways for your application to run in the background.

Now the system is going to apply some limits to the amount of time that you get to run while in the background.

These limits are on the order of seconds, and the system is going to consider the amount of time that you use as well as the amount of CPU you use.

So it's in your best interest to complete your work as quickly and as efficiently as possible.

Now in a later seed, if you exceed these limits, the system will kill your application.

You'll get a crash report, and you'll know whether you exceeded the CPU limits or the time limits based on the exception code in the crash report.

Now we recognize that different tasks may have different needs.

So ApplicationRefresh task and the URLSession task have a little bit longer limits than watch connectivity and your snapshot task.

So in watchOS 2, complications were the primary way that your application got runtime while in the background.

In watchOS 3, we're going to make sure that you continue to get multiple updates an hour if you're a complication application on par with what you were receiving in watchOS 2.

However, if you were previously asking the system for runtime to update your complication data, you can now request updates through WKExtension.

I'll show you how to do that in just a minute.

Also, new in watchOS 3, we're guaranteeing you fifty pushes from your parent iPhone if you're using watch connectivity.

It's really easy to take advantage of this information to make sure that you have a great complication experience for your users all day long.

Let's take a look at some code.

So let's say that you're running an iPhone app, and you notice that your model changed.

You can now query WCSession, remainingComplication UserInfoTransfers to figure out how many high-priority pushes you have left for the rest of the day.

You can use this information to tailor your complication experience and determine when the best time is for you to send your complication data.

So let's say in the default case, you've got plenty of pushes.

Go ahead and send your data immediately.

The user will see your data that's most relevant almost immediately.

Now let's say that you've been pushing a lot, and you're sort of running low on, on transfers.

So you might consider throttling the data that you send to the watch to make sure that your user will have complication data updates throughout the rest of the day.

Finally, if you don't have any high-priority transfers left, it's still okay to try and send this data, however, the data will get sent at a lower priority.

Next, I'd like to talk about some of the CLKComplication DataSource methods that we'd like to move into WatchKit.

If you were previously asking the system for runtime with getNextRequested UpdateDate.

You should now expect the system to schedule a background refresh with a preferred date, the same date that you were telling ClockKit previously.

Similarly, when ClockKit wanted your application to run, it would call requestedUpdate DidBegin.

Now we want to do that at your application level with handle background refreshed or handle backgroundTasks.

You'll get an application task to handle both complication updates and your application updates.

Now, new in watchOS 3, we've introduced the dock.

We think it's a great way for users to quickly get at their favorite applications and have a glanceable view of all of the information that they care about.

We want your applications in the dock to be up to date.

So we're going to guarantee you a minimum of one update per hour.

This applies to a snapshot task and an application refresh task.

Now this budget is distributed across all of the applications in the dock, and the user can pick how many applications they want in their dock.

Consequence of this is that if a user has fewer apps in the dock, then your application can get more opportunities to run in the background during any given hour.

Also, we keep your applications in memory so that resumes are fast, and the user can interact with your application if they settle on it as quickly as possible.

Also in the dock, we have the concept of a most recently used app.

This application occupies the last slot in the dock, and the users are given an opportunity to keep it in the dock by pressing the button.

Now this application is treated exactly like a user's favorite application that the user has explicitly added into the dock.

This means that this application will receive background refresh tasks and snapshot tasks like any other application in the dock.

So you should always make sure that you schedule with the system any application, any background refresh request that you may need.

Now home screen applications shouldn't expect regular scheduling.

So just keep that in mind.

As Eric mentioned earlier, the snapshots of your application are critical to the experience of your application in the dock on watchOS 3.

There may be times when the system needs to snapshot your application for various reasons.

Now if the system asks your application to perform a snapshot because we think we need one, these snapshots don't count against your budget, and they're in addition to the requested snapshots that you've asked of the system.

There are five triggers that can cause the system to ask your application for a snapshot.

If your complication timeline updates, if the user interacts with one of your notifications, this means that the notification was actively dismissed, and it doesn't count if it goes in the notification center.

When you go from the foreground to the background, and then again, one hour later to give your application a chance to return to its default state, if appropriate.

And, finally, in order to get everything started, the system is going to ask your application for a snapshot on boot.

This is your opportunity to start scheduling any other background refresh tasks with the system.

Now, I'd like to take a few minutes to share with you some best practices that we picked up along the way as we adopt a background refresh in our own applications.

So, first of all, the system wants to know as much information as we can about your needs.

So schedule as often as you need to.

Every time your application gets a chance to run, you should consider re-evaluating your background refresh needs and scheduling with the system as appropriate.

You should not feel obligated to do work, however.

If the system calls your application back for a background refresh task, and it doesn't make sense, maybe you just updated your data already, need to do anything else, finish as soon as possible.

Or better yet, in the, in the past when you've done that work, consider deferring any additional work that you've scheduled with the system.

You should consider all the runtime opportunities that you get to make sure that you keep your application up to date.

This means updating your model and your UI and scheduling background tasks for the system.

So for dock and foreground activations, notifications, complication updates, background refresh.

There's any number of reasons why your application may get runtime, and you should keep all of them in mind as you try and keep your application up to date.

So application refresh background tasks is your entry point into general purpose runtime while you're in the background, and we think there's some great use cases for this.

You can do things like pull the system database.

Maybe you need to read the HealthKit database or the calendar database periodically.

You can use this to schedule future URL sessions.

This is what we do in our stocks application.

If you have known time transitions, you can tell the system the exact date that you think that it would be great to run your application.

For example, a calendar application or an itinerary application may have very well defined time transitions.

And, finally, if you were previously getting background runtime through the ClockKit API's, we want you to move to the WatchKit API's to trigger complication updates.

Now let's talk about some best practices for your snapshots.

The snapshot is a system-owned cache of your application's data, and like any cache, that data can become stale.

So the system wants to know when that data is stale.

You can tell the system that your snapshot needs to be updated by scheduling a new snapshot request for now.

Now you should think in terms of significant content change when you're trying to invalidate your snapshot.

You wouldn't want to do something like high-frequency invalidation.

For example, in a timer application that's counting down, you wouldn't want to update our snapshot every single second.

This doesn't make sense.

Instead, you would want to tell the system to update your snapshot when something significant has happened like the timer's ended.

Now I know this is complex, and I'd like to share what I think is a great data flow for how to manage this complexity.

So let's say that you get some external event.

Maybe it's watch connectivity.

Maybe it's NSURLSession.

Maybe you just happened to run in the foreground because the user launched your application.

Basically, anything that causes you to update your model.

All of the operations that we want you to do for background refresh are in response to your model changes.

For example, updating your complication, requesting a new snapshot, and then evaluating what your next background refresh needs are, whether it's for a background URLSession, or just scheduling arbitrary runtime with a background refresh API.

Now with the dock in watchOS 3, we think that users are going to, we think that users are going to be in and out of many applications much more often than they were in watchOS 2.

Now in watchOS 2, you already had to be prepared to enter the foreground or enter the background at any time, but we think these transitions are going to happen a lot more often now.

So you should make sure that you finish any background task as soon as possible on foreground activation.

When your application activates in the foreground, you don't want to be doing any additional work.

You just want to do the work that makes sense for displaying your UI to the user.

Similarly, when you entered background after being in the foreground, you should finish any foreground work that you were doing as soon as possible.

Now we recognize that you might need a little bit of time to complete any foreground work, and you can do this by using NSProcessInfo.


There's a great session from last year's WWDC, WatchKit Tips and Tricks that tells you exactly how you should use NSProcessInfo.


Finally, one more thing I want to mention, data protection.

Now, typically, a user will put their watch on in the morning and unlock it, and the watch will be unlocked all day long until they take it off at night, and put it back on the charger.

Certain types of data on the watch are completely inaccessible while the device is locked.

For example, the most prominent case is the HealthKit database.

So you should just make sure that you consider what your approach is if your data is not available for snapshotting.

And then I'd like to share some testing tips.

The simulator is going to be great for iterative development.

As I mentioned before, we have some budgets, but in a simulator, we're not going to enforce any of those budgets.

So you should basically get your task called at the dates that you want them while in the simulator.

Similarly, while you're on the device, we may still apply some budgets, but you're going to have the best experience while you're on the charger.

You need to make sure that you test both the launch path and the resume path.

The system is going to do its best to keep your application in memory, but in the case of bootstrapping, the system will have to launch your application in order to request the initial snapshot.

Verify that your tasks are being completed.

In a future seed, you'll get a crash report if you fail to complete your tasks in time.

And it's super important that your application doesn't crash because we want your application to be as responsive as possible for users.

And, finally, once you think you have your background refresh strategy implemented, you should live on it.

Make sure that you're getting the experience that you want your users to have.

You should vary the number of applications that are in your dock to make sure that you test the best- and worst-case scenarios for when you'll be scheduled.

Now, I'd like to share a quick case study on how we adopted background refresh in our stocks application.

So before we even got started writing any code, we took a step back and thought about the characteristics that are interesting for background refresh for our stocks application.

We use a URLSession to retrieve server data, and we're going to have a complication.

This means that we know that we have multiple views of our data across the system.

With our complication, our snapshot, and now our live application.

We know that we want to be periodic throughout part of the day.

We want to get regular updates for our application, but then we know something interesting about our data, which is that once the markets closed, our data's good for the rest of the day.

It's not going to change at all.

Well, let's talk about how this looks like throughout the day.

So let's say our device boots.

The system is going to ask our application for a snapshot.

So we'll load our last data, and we'll prepare our UI, but before we complete our snapshot task, we're going to schedule a background at URLSession task.

Now this is our opportunity to start the background refresh cycle, and make sure that we can download the most up-to-date data for our users.

Now we're going to use an NSURLSession DownloadTask so that we can give the system information about what data we want to download, and the system can put our application to sleep and download our data in the background.

Now URLSession DataTask does work on a background session, however, it will fail if your background app or it will fail when your application gets suspended.

And because of the time limits for background refresh, your application is likely going to suspend before your data is available.

So we could recommend using the download task.

So a little bit later, the system is going to wake our application up because we finished our download.

So we're going to update our model, and because we've updated our model, we're going to do three things.

We're going to trigger a complication update, and we're also going to tell the system that our snapshot is invalid by asking for a new snapshot right now, and then we're also going to evaluate what our next background refresh needs are.

So we'll figure out what the next time we want to run is, and we'll tell the system that.

Now a little bit later, we get to run for background refresh, and all we're doing here is scheduling our next URLSession download.

So we complete this cycle several times thoughout the day.

Just keeping our application up to date, the system will snapshot us, and if the user views our snapshot in the dock, we'll have the most recent data available in our snapshot.

Well, let's say that the user activates our app from the dock.

So we go full screen, and we want to make sure that our users have the most up-to-date data.

So we'll download the most up-to-date data again because we've entered the foreground.

And, and once we've updated our model, we still do three things.

We request a complication update, we request a new snapshot, and then we schedule a background refresh again for a later time.

Now there's two things I want to point out here.

First of all, we're in the foreground, but we're still requesting a new snapshot.

This is absolutely okay, and we really expect you to do this.

We want you to request a new snapshot whenever your model changes.

The system is smart enough to know when your application's foreground, and when it's not okay for us to send you a snapshot task.

The second thing is because we run in the foreground and updated our model, it makes sense for us to evaluate our next before refresh needs.

If we knew that we were probably going to run in the next ten minutes, but we've just downloaded our data, we can defer our snap, or we can defer our background refresh request with the system to maximize the amount of time that we get to run.

To maximize the number of opportunities that we get to run.

Finally, the last update after market closes.

We know our data stopped changing for the day, but we'll complete our update as normal.

This means updating our complication, requesting a new snapshot, and then evaluating our background refresh needs.

So because we know our data stopped updating for the day, that we can't have stale data in our complications or our snapshots.

We can wait until the next market open for the next background refresh opportunity.

This lets our application get out of the way of the system and not do any unnecessary work, which means that there's more refresh tasks for other applications on the system.

So to summarize, complete your tasks.

It's absolutely critical to complete your tasks.

If you don't, in a future seed, the system is going to kill your application.

If the system kills your application, users won't have the quick response times in the dock that they're expecting, and users will take your applications out of the dock.

Use all the runtime that you get efficiently.

Consider foreground activations, notifications, ClockKit, and, of course, the background refresh opportunities to run.

Anytime you get runtime, make sure that you consider keeping your model up to date and evaluating your background refresh needs with the system.

Tell the system when your data changes.

Your complication and your application snapshot are both system-owned caches of your application's data.

The system needs to know when that data is no longer valid so that we won't display the wrong things for the user.

Users expect to see consistent data no matter how they view your application's data.

And, finally, you need to consider your adoption strategies on a case-by-case basis.

There is no one size fits all solution.

You have to really consider how users are using your application and interesting characteristics about your data for how you plan your background refresh strategy.

For more information, you can visit this website, and there is some great related sessions this afternoon at 3:00, Architecting for Performance on watchOS 3.

We'll go into some more detail about what we did in the stocks application.

Thank you very much.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US