BGProcessingTaskRequest executing even after force-quit from App Switcher

Hello,

I have a question regarding the behavior of BGProcessingTaskRequest when the app is force-quit by the user via the App Switcher.

Based on common understanding and various discussions — including the following Apple Developer Forum threads:

…it is widely understood that iOS prevents background execution (such as background fetch, push notifications, or BGTaskScheduler) after a user force-quits an app via the App Switcher.

However, in my app, I have observed that a scheduled BGProcessingTaskRequest still executes even after the app has been explicitly terminated via App Switcher. The task is scheduled using submit(_:error:), and it is clearly running some time after the app has been closed by the user.

That said, the task does run, but it appears to operate under tighter constraints — for example, it may be allowed to run for a shorter duration, and network requests appear to be more restricted compared to when the app is not force-quit.

My questions are:

  1. Are there any documented or undocumented exceptions that allow this kind of behavior after force-quit?
  2. Could this be a bug or a behavior change in recent iOS versions? (I am observing this on iOS 18.3, 18.4, and 18.5)

Any insights, experiences, or clarifications from Apple engineers or fellow developers would be greatly appreciated.

Thank you!

Answered by DTS Engineer in 849374022

However, in my app, I have observed that a scheduled BGProcessingTaskRequest still executes even after the app has been explicitly terminated via App Switcher.

...

Are there any documented or undocumented exceptions that allow this kind of behavior after force-quit?

Yes, the behavior here is an intentional choice by the engineering team to work around a very specific (and common) usage pattern. BGProcessingTaskRequests are designed to run when they're least likely to disrupt the user, which, in practice, means they tend to run "in the middle of the night" (say, ~3 a.m.) when the user is charging their phone. However, the problem here is what happens with apps that have a usage pattern like this:

  1. Every morning, the user gets up and runs the app (for example, they're checking the morning traffic).

  2. Later in the day, they force quit the app and they want it "out of the way".

  3. They never launch the app again because "they only run it in the morning".

  4. They go to bed, and the cycle starts all over again in the morning.

Note that this is just one example among many. As another example, it's pretty common for users to end turn-by-turn direction sessions by force quitting the app, simply because it's faster than opening the app and pushing "stop".

In any case, there are many cases where an app clearly "should" be able to use BGProcessingTaskRequest because it's a heavily used app, which the normal force quit behavior would prevent.

With that background, returning to the question...

Are there any documented or undocumented exceptions that allow this kind of behavior after force-quit?

What the BackgroundTask framework does is run the processing task once, even if the app has been force quit, and then ignore that app’s task until the app is run again (which “resets" the behavior). This allows frequently used apps that the users happen to force quit to use BGProcessingTaskRequest, while still preventing BGProcessingTaskRequest from being abused.

*As I noted above, many force quits are as much about interface "convenience" and not any issue/concern about the app being terminated.

Could this be a bug or a behavior change in recent iOS versions? (I am observing this on iOS 18.3, 18.4, and 18.5)

It's definitely not a bug nor is it new. It's always done this.

Some notes on this:

That said, the task does run, but it appears to operate under tighter constraints — for example, it may be allowed to run for a shorter duration, and network requests appear to be more restricted compared to when the app is not force-quit.

  • I don't think there is any specific logic that specifically restricts force-quit apps.

  • The "launch only one" behavior might make it look like there is. For example, if your task fails to complete it won't be allowed to run again until your app has been run again, which could mean that it ends up getting deferred longer than it "normally" would.

  • The system that prioritizes which task to run is complicated enough that it's difficult to predict EXACTLY how things will behave under any specific scenario. Basically, unless you're looking at a very large data set and well-controlled conditions, it's very assumed that differences are caused by some specific "choice" when they're actually caused by normal variation that you can't really see.

Particularly on that last point:

network requests appear to be more restricted

This isn't happening, at least not because of the background tasks framework. They're entirely different API stacks that don't really interact with each other.

*One possible explanation here is that NSURLSession has its own rule around how it handles background transfers, which is that it doesn't do background transfers for apps that have been force quit. I'm not sure how that would interact with BGProcessingTaskRequest, but it's certainly possible that you'd see different transfer behavior with NSURLSession.

Based on common understanding and various discussions — including the following Apple Developer Forum threads: ... …it is widely understood that iOS prevents background execution (such as background fetch, push notifications, or BGTaskScheduler) after a user force-quits an app via the App Switcher.

Again, just to make this clear, the behavior here is specific to BGProcessingTaskRequest, due to its unique role and how that interacts with common user usage patterns. Notably, BGAppRefreshTaskRequest does not relaunch for quit app and never has.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Are there any documented … exceptions that allow this kind of behavior after force-quit?

No. Indeed, there’s no documented behaviour for this stuff at all. This is all implementation details. And those implementation details have changed in the past and will likely change again in the future.

However, in my app, I have observed

I want to make sure that you’re doing an end-user-level test here. So, something like:

  1. Update your app so that it logs key background task events to the system log. See Your Friend the System Log.

  2. On a device with Developer Mode disabled…

  3. Install your app via TestFlight.

  4. Run your test.

  5. When you’re done, trigger a sysdiagnose log.

  6. Look at your app’s log points in the enclosed system log.

That way you know you’re looking at a real phenomenon, rather than an artefact of your development process.

Once you’ve nailed down that, it’s time to decide what to do about these “tighter constraints”:

  • If those cause problems for your app, you can file a bug about them.
  • If not, you can just take the win.

If you do file a bug, please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

However, in my app, I have observed that a scheduled BGProcessingTaskRequest still executes even after the app has been explicitly terminated via App Switcher.

...

Are there any documented or undocumented exceptions that allow this kind of behavior after force-quit?

Yes, the behavior here is an intentional choice by the engineering team to work around a very specific (and common) usage pattern. BGProcessingTaskRequests are designed to run when they're least likely to disrupt the user, which, in practice, means they tend to run "in the middle of the night" (say, ~3 a.m.) when the user is charging their phone. However, the problem here is what happens with apps that have a usage pattern like this:

  1. Every morning, the user gets up and runs the app (for example, they're checking the morning traffic).

  2. Later in the day, they force quit the app and they want it "out of the way".

  3. They never launch the app again because "they only run it in the morning".

  4. They go to bed, and the cycle starts all over again in the morning.

Note that this is just one example among many. As another example, it's pretty common for users to end turn-by-turn direction sessions by force quitting the app, simply because it's faster than opening the app and pushing "stop".

In any case, there are many cases where an app clearly "should" be able to use BGProcessingTaskRequest because it's a heavily used app, which the normal force quit behavior would prevent.

With that background, returning to the question...

Are there any documented or undocumented exceptions that allow this kind of behavior after force-quit?

What the BackgroundTask framework does is run the processing task once, even if the app has been force quit, and then ignore that app’s task until the app is run again (which “resets" the behavior). This allows frequently used apps that the users happen to force quit to use BGProcessingTaskRequest, while still preventing BGProcessingTaskRequest from being abused.

*As I noted above, many force quits are as much about interface "convenience" and not any issue/concern about the app being terminated.

Could this be a bug or a behavior change in recent iOS versions? (I am observing this on iOS 18.3, 18.4, and 18.5)

It's definitely not a bug nor is it new. It's always done this.

Some notes on this:

That said, the task does run, but it appears to operate under tighter constraints — for example, it may be allowed to run for a shorter duration, and network requests appear to be more restricted compared to when the app is not force-quit.

  • I don't think there is any specific logic that specifically restricts force-quit apps.

  • The "launch only one" behavior might make it look like there is. For example, if your task fails to complete it won't be allowed to run again until your app has been run again, which could mean that it ends up getting deferred longer than it "normally" would.

  • The system that prioritizes which task to run is complicated enough that it's difficult to predict EXACTLY how things will behave under any specific scenario. Basically, unless you're looking at a very large data set and well-controlled conditions, it's very assumed that differences are caused by some specific "choice" when they're actually caused by normal variation that you can't really see.

Particularly on that last point:

network requests appear to be more restricted

This isn't happening, at least not because of the background tasks framework. They're entirely different API stacks that don't really interact with each other.

*One possible explanation here is that NSURLSession has its own rule around how it handles background transfers, which is that it doesn't do background transfers for apps that have been force quit. I'm not sure how that would interact with BGProcessingTaskRequest, but it's certainly possible that you'd see different transfer behavior with NSURLSession.

Based on common understanding and various discussions — including the following Apple Developer Forum threads: ... …it is widely understood that iOS prevents background execution (such as background fetch, push notifications, or BGTaskScheduler) after a user force-quits an app via the App Switcher.

Again, just to make this clear, the behavior here is specific to BGProcessingTaskRequest, due to its unique role and how that interacts with common user usage patterns. Notably, BGAppRefreshTaskRequest does not relaunch for quit app and never has.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I truly appreciate your responses — I wasn’t expecting such a fast and clear answer. Thank you for taking the time to help!

First of all, I'm always grateful for your help, Eskimo, and I’m truly honored to have received a response from you this time.

I want to make sure that you’re doing an end-user-level test here. So, something like:

I’ve confirmed that the behavior of BGProcessingTaskRequest remains the same even when the app is distributed via TestFlight and running in non-developer mode.

I hadn’t considered the possibility that behavior might change in developer mode, so I’ll definitely keep that in mind for future testing.

The behavior here is an intentional choice by the engineering team to work around a very specific (and common) usage pattern. BGProcessingTaskRequests are designed to run when they're least likely to disrupt the user, …

Thank you so much, Kevin — your explanation helped me clearly understand the intended use and behavior of BGProcessingTaskRequest.

I’ve realized that the behavioral differences between a suspended app and a force-quit app are not dependent on BGProcessingTaskRequest, and that task failures can affect future executions.

This insight is extremely helpful in designing the background task implementation for my app.

One possible explanation here is that NSURLSession has its own rule around how it handles background transfers.

In fact, the background processing in my app stops when using URLSession. I’m going to review my app’s workflow in background accordingly.

Additionally, I previously had some doubts about how BGProcessingTaskRequest differs from BGAppRefreshTaskRequest, and your explanation made that distinction very clear.

Once again, thank you all so much for your support. I really appreciate all your guidance.

Thank you so much, Kevin — your explanation helped me clearly understand the intended use and behavior of BGProcessingTaskRequest.

I actually have one correction to what I said here:

"What the BackgroundTask framework does is run the processing task once, even if the app has been force quit, and then ignore that app’s task until the app is run again (which “resets" the behavior)."

This was how it originally worked, but after talking to the engineering team yesterday, the "just once" behavior has mostly been removed as unnecessary*. Currently, scheduling is being driven primarily by overall app usage patterns. So it's entirely possible for BGProcessingTaskRequest to run multiple times in a very frequently used app.

*Real world testing showed that app usage patterns alone prevented the API abuse the team was concerned about.

In any case, the basic idea here is that BGProcessingTaskRequest operates outside the normal app lifecycle. Its goal is to allow the apps a user relies on to perform maintenance work in the background, so what actually matters is how much the user uses the app, not whether or not the user happened to have force quit the app the last time it ran.

Two other quick comments:

I’ve realized that the behavioral differences between a suspended app and a force-quit app are not dependent on BGProcessingTaskRequest, and that task failures can affect future executions.

One thing to be aware of here is that the "noise" level in the data can be really high here. For example, on real-world devices, it's not unusual to see wide variation in performance between task runs, even though the device and network are exactly the same. That's not because of any specific OS behavior; it's just a different mix of jobs are dividing up the same fixed resources (CPU/Network/etc.).

Also, if you haven't already, please take a look at this forum post on task scheduling on dedicated development devices. Something that's confused many developers is that the fact that task scheduling is driven by app usage patterns means that the scheduling behavior on dedicated development devices can end up being REALLY weird.

In fact, the background processing in my app stops when using URLSession. I’m going to review my app’s workflow in the background accordingly.

You may want to take a look at this thread on networking in processing tasks. Many developers assume that because processing tasks run in the background, that means they should use the background URLSession, but that isn't the case at all. One of the use cases BGProcessingTaskRequest was designed to support letting apps reliably complete "larger" network transactions without worrying about the unpredictability the background session can create. You should generally have several minutes of execution time, which is more than enough to transfer 10s/100s of MBs.

More to the point, the primary download window for the background URLSession... is exactly the same execution window processing task uses, so using the background session isn't really any "better". It's perfectly reasonable to use processing task to set up lower priority schedule bulk transfers, but it's also fine to just use the standard configuration for immediate transfers.

Once again, thank you all so much for your support. I really appreciate all your guidance.

You're very welcome.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

BGProcessingTaskRequest executing even after force-quit from App Switcher
 
 
Q