Advances in App Background Execution

Session 707 WWDC 2019

Background execution is a powerful tool your app can leverage to provide a great user experience. Learn about best practices to follow when running in the background, especially if you use VoIP or silent pushes, and an all-new scheduling API that enables long running processing and maintenance tasks.

[ Music ]

[ Applause ]

Hi. I'm Roberto from the Software Battery Life Team here at Apple, and I'm really excited to talk to you guys today about advances in App Background Execution.

Users love apps, and they love apps because of the many great experiences that they enable.

Some of these are in the foreground when the user is actively using the app, and some of these are in the background and require background execution in order to be possible.

So, if we come up with a list of what might require background execution, we might come up with something like this, things like navigation or accessory communication or maybe periodic updates and downloads.

And at Apple, we design APIs that give background execution to enable these use cases and experiences.

So, today, we'll talk about background execution, give a general overview, then talk about some best practices to follow, and finally, my colleague, Thomas, will come up to introduce a new background task framework that offers new background execution opportunities.

Let's begin with an overview of background execution.

We can begin by answering the question of what is background execution?

What do we mean?

So, when we think of background, it can mean many things.

Background threads or background queues, but when we're talking about background execution, we're talking about the app running or executing code while it's not in the foreground.

So, looking at this diagram, we're talking about that third box where the app is running in the background but it's not necessarily visible to the user.

Now, why do we enter this state?

It really boils down to two ways.

The first is a request by the app.

This applies to more generic background execution where the app wants to perform some work and it makes a request to the system.

You can think of something like downloads or periodic updates or maybe finishing some foreground work.

The second way is through specific event triggers where the app gets background time in order to respond to something that happened in the world.

For example, maybe the user entered some foreign region or maybe there was some new health data that your app needed to be made aware of.

And when running in the background, we care deeply about user experience, so there are many important factors we consider when we design APIs to support these use cases.

And I want to highlight three of these.

Power, performance, and privacy.

So, let's start with power.

Whenever your app is running in the foreground or the background, it's using power, and over time, this drains energy and can drain battery.

To visualize this, let's take a timeline.

The left might be the start of the day when the sun rises, and the right might be the end of the day, and maybe the user decides to plug in their device towards the end of the day, and we have that green charging region to represent that.

So, we can plot the times our app runs in the foreground in these lightly shaded rectangles, and we can plot the time the app runs in the background in these darkly shaded rectangles.

If we run for longer, then we'll drain more battery.

If we run for less time, we'll drain less battery.

And so, when we design APIs, we focus on the right amount of time to run to support specific use cases while maintaining great battery life, and when using these APIs, think about how to be efficient and alert the system when you're done doing work by calling completion handlers.

That way, if the system gave your app time to run, you can tell the system, hey, I'm done a little early.

It can suspend your app, and you'll stop consuming the user's battery.

Next, we have performance.

We want our system to perform as smoothly as possible.

This means that we want fast app launches and responsive UI, and this is especially important when we're running in the background.

And the reason is that while we may be tempted to think that there's only one app running over the course of the day, there actually are multiple apps running at different points in time in the foreground and the background.

And when we overlay this, we can see that our app may be running in the background while another app is running in the foreground or multiple apps can be running in the background when an app is running in the foreground.

And so when we design APIs, we think about setting smart CPU and memory limits to minimize the impact on other usage, and when using these APIs, you should be aware of what these limits are so that you don't affect what the user is actively doing but more importantly so that the system doesn't terminate your app, and then when you launch in the future, you might be slower to launch.

The last important factor is privacy.

Users are really sensitive and really care about their personal data.

And, while they may be aware of all the times that the app is running in the foreground and expected to have access to certain pieces of information, they may not be as aware of all the times the app is running in the background.

And so, when we design APIs, this means that we have different APIs for different use cases, each with their own set to a specific set of data that it needs to support that.

And when using these APIs, think about how to be transparent to the user and let them know which pieces of data you're using.

So, those were the three important considerations to consider in background execution.

Power, performance, and privacy.

And when we go back to our list of use cases, these different use cases translate into different APIs, each with a different set of requirements to achieve the desired behavior while maintaining a great user experience.

So now that we have a general overview, let's dive into some best practices to follow for some specific modes and talk about changes to APIs.

So, to do so, let's take a messaging app.

It might have core functionality, like sending messages or making phone calls, and then it might have some bonus features like allowing the user to mute a chatty thread or downloading past attachments.

And so now, I'll go through each of these four and talk about which API we would use to accomplish this.

The first is sending messages.

Sending messages is a core functionality of your app.

If I'm sending a message to my friend, I want it to be delivered right now, not a day from now or a week from now.

And so the user expects that this will complete immediately.

And while this may usually be very quick, sometimes this might not be the case.

Maybe the network's congested or maybe the backend servers are slow, and it might take additional time to send the message.

And in this additional time, the user might even leave the app or put their phone down and lock it.

And so, we need a way to protect the completion of this task.

We need to make sure that our message finishes sending so that the user doesn't come back to the app and realize, hey, why didn't my message send to my friend.

The API to use for this is background task completion.

This gives apps additional run time to run in the background before being suspended.

To do it, you call UI Application Begin Background Task or you call Process Info Perform Expiring Activity if you're running from an extension.

And again, this is to complete work that started in the foreground.

So, other examples might include saving files to disk or completing any user-initiated requests.

So, let's take a look at what this might look like in code for our sending messages example.

Here, we have our send message function.

In it, after we establish our send operation, we call Begin Background Task.

This lets the system know that, hey, we're starting a task that even if the user backgrounds the app, let us continue running to finish it.

Then, when we finish sending the message, in our completion block, we'll let the system know to end the background task.

We don't need time anymore.

This is for cases when the system might have given us time, the user backgrounds the app, and we're done, and we can tell the system to suspend us so that we stop draining the user's battery or potentially affect their performance.

And there's one last thing I want to highlight.

There are some cases where maybe the system gives you additional time, but conditions were so bad, maybe it was so congested that even with the additional time we didn't finish.

And so, we have an expiration handler for that, and the system will call the expiration handler at this point in time.

So, in this case, we post the user notification to the user, a local banner saying hey, hop back into the app, because the message didn't send.

So, to recap, when we're sending messages, we want to protect that it sends by using background task completions, and we want to make sure we start it based on the user action.

We don't want to wait until we enter the background to call begin background task, because if we do so, then we're limiting the amount of time we're actively trying to send it before the system may suspend our app.

So, now that we've talked about sending messages, let's talk about phone calls.

So, I may want to message my friends all the time, but sometimes you just want to get on the phone and quickly hop in a phone call and tell them something so you don't have to type it out.

There's an API for this, and it's VoIP push notifications.

This is a special type of push that launches your app and gives it run time so that you can present to the user the fact that they're being called, and the user can answer a phone call.

To register for this, you simply set the VoIP push type in your PK push registry when you register for your VoIP pushes.

And new this year, it's very important that you know that you must report incoming calls with CallKit in the didReceiveIncomingPush callback or your app will be terminated.

And, if you repeatedly do this, or if you repeatedly fail not to report an incoming call, the system may stop launching your app for VoIP pushes altogether.

So, let's take a look at how we might adapt to this change in code.

Here, we have our didReceiveIncomingPush callback, and in it, we see that if the push type is VoIP, we'll use the information from our push payload to populate a CX call update object, and then we report a new incoming call using our CX provider.

And so, you need to make sure that you report your incoming calls before that method returns, or your system, the system will kill your app.

A couple other tips you might want to take note of is that if you include your caller info in the push payload, then you'll have all the information you need to quickly present that incoming call UI, and so try to include as much information as possible so that you can present as rich of a UI as possible.

Second, make sure that you set an apns-expiration on your push to 0 or something small.

In this way, the user won't receive a push minutes later for a call that is no longer dialing.

So, for example, if someone called my device, and the push took two minutes to send, then I wouldn't want to report an incoming call once the push comes in two minutes later because that person is probably not dialing anymore.

So, if we set the expiration to 0, which means deliver immediately or fail or something on the order of a few seconds, then we know that if the device is receiving a push, it's still for a phone call that's relevant.

And, it's important to note that if you prefer a banner, you can always just use standard pushes instead of the full and that way you don't have to a full screen in call UI.

And you can use a notification service extension if you need to modify the content of your pushes.

For example, if you needed to Decrypt them.

So that was VoIP pushes and phone calls.

Now let's talk about muted threads.

So, when a user has a messaging app, they might be messaging a lot of different friends or a lot of different groups of friends, and some of these threads can get pretty chatty, and the user might not want an alert for that specific thread.

But, the content may still be relevant.

It may still be nice to have.

When the user hops back into the app, they want to be able to see the message.

They just don't want to be told every time the message is coming in by having their device vibrate.

And so we need a way to alert the device but not the user about content being available.

To do so, you should use background pushes.

Background pushes are a mechanism to tell the device that new data is available without alerting the user.

To use these, you just send a push with the content available flag set to 1 without an alert, a sound, or a badge.

And then the system will intelligently decide when to launch the app to download the content based on power and performance impacts and trying to minimize that.

So, to look at this on a timeline, it might look something like this.

The user might foreground the app and decide to mute a specific thread.

Then at some point in the future, someone might message that thread, and a background push will be received by the device.

Sometime after that, the app will be launched and get some run time to fetch that content, and then when the user comes back to the app later on in the day and foregrounds it, the user will open the thread and see that the content is right there.

And, there's some very important new things with background pushes too.

You must set the apns-priority to 5 or your app will not launch, and you should also set the apns push type to background.

This is required for watchOS but we highly recommend it for all platforms.

And if you want more information about pushes on watchOS, you can see the creating independent watch app session from yesterday.

So to recap, for muted threads, use background pushes as a best effort way to download the content, and in cases where maybe the app didn't runtime after the background push was received, you can always just download that content when the app re-enters the foreground.

Now, let's talk about downloading past attachments.

Say the user signs into their account, and it's a new device.

There might be some content that they immediately want from their account, like their conversation list or some recent messages.

But there might be a bunch of older content that it would be nice if you didn't have to download it right when the user was in the app.

To visualize this, why download it while the user is in the foreground and potentially impact their performance or battery life when we could do it at some point when the device is charging and idle.

The way to do this is to use a discretionary background URL session.

This allows the system to defer a download until a better time, and it's an API that allows you to pass more information so that it can be smarter about scheduling it.

To use it, you simply set up a background URL session the way you would normally, and then you set your discretionary flag to true.

Let's take a look at some of those additional information that you might be able to pass in so that the system can be smarter.

You can pass in timeout intervals.

Maybe you don't want it to keep attempting to download something, so you want to bound how long it does it.

You could pass in an earliest begin date.

Maybe you don't want to do an upload or a download until some point later in the future.

And, you can pass in an expected workload size so that the system knows how much work it should expect to do when it runs your download.

So, when you're downloading past attachments, you want to defer the work if possible to minimize the user-visible impact, and we can apply the same principles to any deferrable download or upload.

So, maybe you have some analytics you want to batch together and upload at a better time, or maybe you have some photos the user took that you want to back up later on.

So, to recap, we had a messaging app, and it features some core functionality.

You can send messages, make phone calls, allow the user to mute threads, and you can download past attachments.

And we used a different API to accomplish each of these.

We used a background task completion to guard that our message would send even if the user left the app.

We used VoIP pushes as a way to enable phone calls.

We used background pushes as a best effort way to give the app runtime to respond to new content, and we used a discretionary URL session to download attachments at the right time.

But there are still many other uses cases that are not currently covered by our existing modes, and so I'd like to invite my colleague, Thomas, up to introduce a new background mode and framework designed specifically for these use cases.

Thanks everyone.

[ Applause ]

Thanks, Roberto.

So let's talk about these use cases.

These are things like actively syncing state with the server, cleaning up your databases, or backing up data to the Cloud.

These are deferrable, maintenance-type tasks that ideally you would want to do in the background in order not to impact foreground user activity.

And what we see today, is that apps are doing this kind of work right as they enter the background, and over the course of an entire day, that can really add up.

So, what if you could take all that work and defer it to later, perhaps when the device is on charger and otherwise idle?

That's what we're introducing this year with a brand-new background mode and framework to go along with it that we call background tasks.

Background tasks let's you schedule work to do in the background later.

We're making it available on iOS, iPadOS, tvOS, and iPad apps on the Mac.

And not only are we introducing this new background mode we call background processing tasks, we're also taking this opportunity to refine the existing API for background app refresh.

So, let's talk about the new background mode.

Background processing tasks give your apps several minutes of runtime at system friendly times so you can do that deferrable maintenance-level work I mentioned earlier as well as entirely new things, such as on-device, Core ML, training and inference in the background, which you can learn about in their session.

CPU Monitor is a feature in our systems that automatically terminates apps that use too many CPU cycles in the background in order to protect user's battery life.

For the first time ever, we're giving you the ability to turn that off for the duration of your processing task so you can take full advantage of the hardware while the device is plugged in.

And finally, we'll make sure that you're eligible to run these tasks as long as you request them when your app is foreground or if your app has been recently used in the foreground.

Next, I'd like to talk about the new API for background app refresh.

This is a new API, but it has the same policies as the existing one, which means your app will get 30 seconds of run time, each time it's launched, to fetch new content and keep itself up to date throughout the day.

How often your app is launched and at what times depends on how the user has historically used your app.

So, if the user typically uses your app in the morning, the afternoon, and the evening, the system will learn this pattern and start launching your app shortly before those times so you have an opportunity to get the content you need.

This also means that an app that isn't used as frequently may not launch as frequently as well.

As I mentioned, this is the new API for background app refresh, so we're deprecating the existing ones on UI Application seen here.

These APIs will continue the work the same way they do today on iOS, iPadOS, and tvOS devices but they're not supported on Mac, so make sure to adopt background tasks for background app refresh on Mac.

Let's take a look at how this API works.

Let's say we have an app and a couple of extensions that's contained within.

The primary object that you will be interacting with is the BG Task Scheduler.

The BG Task Scheduler is your interface to the systems intelligent, dynamic activity scheduler that constantly monitors various system conditions including battery level, app usage, network connectivity, and more.

While your app is running, you can request that it be woken up later to do work in the background.

To do so, you'll create an instance of a BG task request object that corresponds to the type of task you would like to perform.

In this case, we want to do a background app refresh so I made a BG app refresh task request.

And we submit that to the scheduler.

If you have several types of tasks you want to do, you can submit multiple requests.

In this case, we also want to do some database cleanup, so I'm going to make a BG processing task request and submit it as well.

You can also submit requests from an extension while it's running.

So, if our keyboard extension wants to do some learning based on the user's typing habits, it can create a BG processing task request and submit it too.

As you can see, you can have multiple pending BG processing task requests, each representing a specific task you would like your app to do.

Now the scheduler knows about all the tasks our app would like to do, and when all the necessary system conditions and policies are satisfied, it's going to fulfill that task by waking up your app and launching it into the background and deliver it a BG task object corresponding to that task you should perform.

In this case, we got a BG app refresh task, so we can now perform a background app refresh, fetch content, and update our UI.

When we're done, we're going to call set task completed, and that's going to mark the task complete and allow your app to suspend.

Depending on how you configure the BG task request and on various system conditions and policies, we may choose to launch your app for multiple tasks at the same time.

So, in this case, our app is launched and delivered both BG processing tasks requested earlier.

When your app is launched, it's given a finite amount of time to complete the work it's given, and that time is allotted per launch, not per task, so you should be prepared to concurrently handle all the tasks you're given in the time allotted.

Also, note that that processing task requested from the keyboard extension was delivered to the main app, and that's because it's always the main containing app that is launched to handle background tasks, never extensions.

When your app is done doing the necessary work in call set task completed on all of the BG task objects it's given, it'll suspend.

So that's a high-level overview of how to use the background task API.

You create and submit BG task requests to the BG task scheduler, wait for the system to launch your app into the background, perform the necessary work, and then call set task completed on the BG task objects.

And to walk you through how you would implement this in your app, I'd like to step you through a demo.

[ Applause ]

So this is our app.

It's called Color Feed, and it's a typical social media type app except instead of a feed of photos, you get a feed of the latest trending colors.

And what you can see is that I can scroll through and see what the latest colors are at various points in time.

So, what I would really like to do is make sure that my app can keep itself up to date and refresh itself throughout the day without the user having to go in and manually do that.

And the perfect tool for this is background app refresh.

So, I'll implement it with background tasks.

The first thing you need to do is add the required keys to your info.plist to declare your support for background tasks and background app refresh.

So, I'm going to go here into my project settings, click on the target for my app, and go into the signing and capabilities tab.

I'm going to click the plus sign and add a new capability for background modes.

As you can see, it added this new section, and we need to choose the right background mode for this type of task.

In this case, for background app refresh, the box is labeled background fetch, just like with the old API.

So, I'll go ahead and check that.

Next, I'm going to head into my ask info.plist file and I'm going to click the plus sign to add a new key.

This key is called permitted background task scheduler identifiers.

And it's an array of strings.

So, each string in this array uniquely identifies a specific task your app wants to do, and it should be unique within your app.

We recommend using reverse DNS notation to avoid conflicts with any third-party frameworks you may be using.

So, here, I'm going to expand that array and click the plus sign to add a new string.

And I'll call it com.colorfeed.refresh and hit save.

Next, I need to actually implement the code to handle when my app is launched.

So, I'm going to do that in my app delegate.

I'll head into my app delegate file, and the first thing I need to do is import background tasks.

All right.

So, the next step is to tell the system what you want to do when you get launched, and you do this by registering a launch handler before your application finishes launching.

So, in my application did finish launching with options method, I'm going to call BG task scheduler shared, register for task with identifier and then pass in that same identifier I just put into the info.plist.

The next parameter is a dispatch queue, which is what my handler will be called on, and I can specify a queue that I'm using if I want to synchronize with other things in my app.

Or, I can pass in nil, and the system will create a background serial queue for me.

The next parameter is the actual launch handler, which is what will be called when my app is launched for the task.

As you can see, it takes one parameter of type BG task.

What I'm going to do is I'm going to call a method I'll write called handle app refresh and downcast that BG task object to a BG app refresh task since this is used for background app refreshes.

I'm going to go ahead and write that handle app refresh method now.

Here, I've written all the code I need to actually perform the background app refresh.

It's going to fetch the latest content from the server, update my database, and update my UI.

But to integrate this with background tasks, there are two things I need to do.

The first is to handle expiration.

Your task is given a limited amount of time to finish, so when your time is almost out, we'll warn you and give you a chance to quickly wrap everything up and complete your work.

The system may also choose to terminate your task early in case the system decides the conditions are no longer sufficient for you to run your task.

So what I'm going to do is set an expiration handler on the task, and in the expiration handler, I'm going to call cancel all operations on my operation queue, and that's going to stop all the work I'm doing and cancel any work that I haven't even started yet.

The next thing I need to do is to call set task completed on the task when I'm done with my work.

I need to do this even if the expiration handler has been called.

And if I fail to do this, the system may actually terminate my app, and that will impact launch performance later, and I definitely don't want to do that.

So, what I'm going to do is I'm going to say that when my last operation in that queue is complete, I'm going to call set task completed, and I'm taking advantage of the fact that operations always call their completion block regardless if they were cancelled early or they finish normally, and I'm using that to determine if my task completed successfully or not.

All right.

So the last step is to actually schedule a BG task request, and I'm going to do that when my app enters the background because that's when the user has stopped using my app.

So, I'll go ahead and write that now.

As you can see, I'm calling a method I just wrote called schedule app refresh, and my application did enter background method.

And here, I'm creating a BG app refresh task request object and passing in again that same identifier.

I then submit to that request to the being task scheduler.

There's one additional property I want to call out on the being task request object, and that's earliest begin date.

This is how you can specify a start delay for your task.

So, in this case, I'm saying don't start app refresh, don't start refreshing my app until at least 15 minutes from when I schedule it, and this lets me get the same behavior as with the old set minimum background fetch interval API.

So, I schedule my task, but there's actually one more thing I'll probably want to do.

Because every single BG task request object corresponds to exactly one launch, right now if my app gets launched for a background app refresh, it won't get launched again until the user enters and then leaves my app.

But I don't want that.

I want it to keep refreshing throughout the day.

So, all I have to do is call schedule app refresh in my handle method.

So, I'll schedule another request as soon as I get launch for an existing one.

And that's it.

That's all the code I need to write to handle background app refresh in my app.

But, as I'm using my app and I'm scrolling through, I'm seeing that there are like a lot of entries from a really long time ago, things that are probably not relevant to the user anymore, and just taking up disk space.

So it would be really great if we could clean our database up for the user, and the best tool for that is BG processing tasks.

So, I'll go ahead and implement that as well.

Just like before, I'm going to head into my project settings, the signing and capabilities tab, and this time, in background modes, I'm going to make sure to check the background processing checkbox.

I'll go into my info.plist and hit the plus sign to add a new identifier.

This one I'll call com.colorfeed.dbcleaning and hit save.

Then, just like before, I'll go into my app delegate and call register again.

Here, it's the same call I made earlier, except I'm passing in the new identifier, and I'm calling handle database cleaning and down casting to BG processing task instead.

I'll go ahead and implement handle database cleaning now.

I've already integrated this code with background tasks, and what it does is it just deletes everything that's older than the past day.

As you can see, I make sure to set an expiration handler and cancel all my operations, and I make sure to call set task completed with success when I'm done.

One thing I am doing differently here is I'm keeping track of the last time I successfully cleaned my database.

I'm doing this because when I schedule my task, I want to be conscientious of how I'm using the system's resources.

I don't want to schedule a database cleaning task every time the user leaves my app if my database doesn't actually need to be cleaned.

So, what I'm going to do is write a method called schedule database cleaning if needed.

And here, I'm just checking that if it hasn't been at least a week since I last cleaned it, don't do anything and bail out immediately.

Otherwise, I'll go ahead and schedule that request.

I'll create a BG processing task request and pass it that same identifier I did before.

There are some additional properties on the BG processing task request you may want to be aware of.

The first is requires network connectivity, and this actually defaults to false.

You should make sure to set this to true if you actually require network connectivity for your task because otherwise we may launch your task at a time where there is no network and you won't be able to do much work.

In our case, we're doing some local database maintenance, so we can just leave this as false.

The next is requires external power.

Now, depending on the specific type of device and various system conditions and policies, we may tend to launch apps while they're plugged in and on charger anyway.

However, if you require, if you are requiring yourself to do intensive work and use a lot of resources, we highly recommend that you set this to true so that you can preserve your user's battery life.

Setting this to true is also how you'll disable CPU monitor for CPU intensive tasks.

And that's it.

I'm going to go ahead and submit that request through the scheduler, and I'm going to do that, if I need to, when my application enters the background.

So that's all the code I need to write to handle both background app refresh and background processing tasks.

But how do I know this all works?

I'd like to think I'm perfect program, but that's obviously not always the case.

So, the best way to test this end to end is to live on your device and use it like a user would, and that will make sure that you can get the time and policies that you need for your specific task.

But, we also know that you don't have time to just sit there and wait when you're coding, so we added a couple of methods you can call in a debugger to debug your use of background tasks, and let me demonstrate that for you now.

I'm going to build and run this new version of background tasks onto my iPhone.

As you see, it launched, and now I'm going to send it into the background to make sure that my task requests are scheduled.

I'll bring it back, and here, I'm going to click the pause button right here on the bottom.

This will enter the debugger.

And I can see the console right here.

What I'm going to do is I'm going to paste in the command that's available in our documentation so no need to write this down.

Here, all I have to do is replace the word task identifier with the identifier of the task I want to simulate.

I want to test background app refresh, so I'll type in com.colorfeed.refresh.

But the steps are the same for my processing task as well.

I'll hit enter, and as you can see, the system has recognized that I want to simulate a launch for my background app refresh task.

I'll hit the play button right here.

And as you can see, the system is starting my app refresh task, and my app is actively refreshing, and I can see that it's working.

I'm going to hit pause again, and this time I'll make sure that expiration works as well, because that's equally important.

What I'm going to do is I'm going to enter that same command except this time I'm going to replace the word launch with expiration and hit enter.

As you can see, the system recognized that I made this request, and when I hit play, you should see that my app has successfully stopped refreshing, recognized that it expired, and mark the task complete.

So, I know now that my app has both successfully implemented and tested background app refresh and background processing tasks with background tasks.

[ Applause ]

Thank you.

There are some additional considerations that you may want to be aware of when you use our framework.

First, be aware of how you set earliest begin date, and don't set it too far in the future.

If you set it too far in the future and the user doesn't come back to your app in the meantime, we may choose to not launch your task at all, and that's just to protect users expectations and privacy.

A user that doesn't use your app for months does not expect that it suddenly starts running in the background.

So, we recommend that you keep earliest begin date, your delays we earliest begin date, down to a week or less.

Next, make sure that any files you need to access during a processing task are accessible when the device is locked because that's the time when we'll typically launch your task.

We do guarantee that we won't start your task until the user first unlocks their device, so make sure any files you need to access are at most file protection type complete until first user authentication.

We showed you how to implement background app refresh in a traditional single-window UIKit app, but as you know, this year we're introducing multiwindow apps with a UIScene API.

If you're adopting that API, you should make sure to call UI application request scene session refresh at the appropriate time to tell the system that the snapshot in the app switcher needs to be updated.

And you can find out details about this in their documentation.

And finally, BGTaskScheduler.submit is a blocking, synchronous call, and we designed it that way because that simplifies adoption when you're scheduling, when you enter the background, which is the common case.

However, if you plan on scheduling during more performance-sensitive scenarios such as when the app is being launched, you'll want to make sure to call it on a background queue so you don't block the main thread and impede your launch performance.

All right.

So, to sum up what we talked about today.

Be really thoughtful about how you use your time in the background and keep in mind, the same things we do when we design our APIs, things like power, performance, and privacy.

Make sure to use the right background mode to accomplish the exact thing you want to do and enable the exact user experience you want.

And finally, use a new background task API to schedule work to do in the background later.

Use BG app refresh task to keep your app content up to date throughout the day and BG processing task to do deferrable maintenance-level work when the device is idle and on charger.

For more information, stop by our labs or visit our session website, for links to documentation and sample code.

Thank you.

[ Applause ]

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