General:
DevForums tag: Background Tasks
Background Tasks framework documentation
UIApplication background tasks documentation
ProcessInfo expiring activity documentation
watchOS background execution documentation
WWDC 2020 Session 10063 Background execution demystified — This is critical resource. Watch it!
WWDC 2022 Session 10142 Efficiency awaits: Background tasks in SwiftUI
iOS Background Execution Limits DevForums post
UIApplication Background Task Notes DevForums post
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Background Tasks
RSS for tagRequest the system to launch your app in the background to run tasks using Background Tasks.
Posts under Background Tasks tag
140 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi everyone,
We came an issue that, In some scenarios in our app we cannot fetch any resources from device (Photo and Contact).
One case we catched is putting app in background and spending time in other commonly used apps and coming back to our app cause this issue but there is a small chance that get this issue during using the application.
In cell, we are trying to fetch the image like this
imageFetchTask = Task {
let image = await CompositionRoot.shared.photosManager.image(requestType: .imageCollections, forId: photoAsset.photoId)
self.photoImageView.image(image)
}
and inner layers of this code we get the PHAsset and request image
PHAsset.firstAsset(for: id)
let manager = PHImageManager.default()
manager.requestImage(for: asset, targetSize: request.targetSize, contentMode: .aspectFill, options: request.options) { (image, info) in
continuation.resume(returning: image)
}
We figured out that issue not happening only in Photos also Contacts and
any web request. So any help according to this situation is well appreciated.
Thanks
I have an iOS app, watchOS app, and iOS Widget that shows the most recent data in the database.
The watch app sends data to the iOS app over the WCSession and is received in session(didReceiveMessage, replyHandler). After that data is processed, reloadAllTimelines() is called.
When running in Simulator or on device plugged in to debugger, it works, the widget updates when the app is closed (in background, even if force quit).
But when running TestFlight or App Store build, the data is still processed and saved to Core Data (I open the app and it's there), but the widget doesn't update.
It seems that reloadAllTimelines only works when the app is in foreground (at least in non debug builds). I dont have an iOS 17 device to check but I think this is a recent bug with iOS 18.
Hi everyone,
I’m encountering a recurring issue with my app submission, and I’d appreciate your insights. My app has been rejected due to Guideline 2.5.4 with the following feedback:
Guideline 2.5.4 - Performance - Software Requirements
The app continues to declare support for location in the UIBackgroundModes key in your Info.plist file but we are unable to locate any features besides employee tracking that require persistent location.
Using the location background mode for the sole purpose of tracking employees is not appropriate.
Please note we located the features of the app but the location background tracking of employees is not appropriate with this guideline.
Next Steps
If the app has a feature besides tracking employees that requires persistent location, reply to this message and let us know how to locate this feature. Otherwise, it would be appropriate to revise the app to include additional features for your users that require the persistent use of real-time location updates while the app is in the background
My App’s Use Case:
The app is designed to support events where users can check in and check out. Persistent location tracking is essential for the following:
1. During Events:
• Tracking users’ real-time location ensures they remain within the event boundaries.
• If a user exits the designated area, the system logs the occurrence for compliance and security purposes.
2. Workforce Monitoring:
• For work events, the app records working hours based on their presence within the event area.
• This ensures accurate logging of attendance and work durations.
Steps I’ve Taken:
• Limited Scope of Tracking: Persistent location tracking is active only during event check-in and check-out periods. Outside of these periods, tracking is disabled.
• User Consent: I’ve implemented clear permission requests and a privacy policy to explain how location data is used.
• Info.plist Configuration: I’ve declared the UIBackgroundModes key with location to support background tracking.
Despite these measures, my app continues to be rejected with the feedback above. I believe my app’s features align with the guidelines as the location tracking is directly tied to event functionality and user benefit.
Questions:
1. How can I better explain this use case to Apple’s review team to demonstrate compliance?
2. Are there any additional features or adjustments I should consider to ensure my app meets the guidelines?
3. Has anyone faced a similar issue with persistent location tracking, and how did you resolve it?
Thank you for your guidance and support!
I'm making a Safari extension for learning languages. I need speech synthesis for any language the user chooses to learn.
I initially tried to make this work within JavaScript, but Safari 18 doesn't reliably list voices for all languages on the web SpeechSynthesis API as described here: https://stackoverflow.com/questions/79179072/how-do-you-use-a-japanese-voice-with-speechsynthesis-in-safari-ios-18
As a workaround, I've had to use AVSpeechSynthesizer in SafariWebExtensionHandler (NSExtensionRequestHandling implementation for the extension). This works in the simulator but not on a real device. I've found this note from Apple in a StackOverflow reply:
"Safari extensions are very short-lived, hence not fit for audio playback or speech synthesis. Not being able to validate an app extension in Xcode with a manually-added plist entry for background audio is the designed behavior. The general recommendation is to synthesize speech using JavaScript in conjunction with the Web Speech API."
Unfortunately, the suggestion to use the Web Speech API is unsuitable as I just explained.
Is there a way to set up a background process in the host app that can do speech synthesis? The app extension would need a way to communicate with this process, and start it if it's not running. Is that possible?
I am trying to build a chat app. I am using FCM to deliver messages to my app accompanied by some custom data like the new message_data, deleted message_id and so on; each message will need to run the app in the background to do some background processing and local database syncing.
This continuous background processing is clearly not acceptable as APNs imposes a per-device limit on background push notifications . I am asking how can I push messages and actions payload without being throttled ?
It’s been established that generally speaking background apps cannot record audio while the foreground app is already reading audio data from the microphone, but are there exceptions? For instance, is there an exception for certain Apple apps?
If so, and there’s a special exception that most programmers don’t know about but some Apple’s engineers do and perhaps some hackers do as well, wouldn’t the mechanism that allows that eventually be exploited?
All the nuances of when and whether a background task runs aside, does launching the app cancel the currently scheduled refresh task? As an example, consider the following case:
8AM - user launches app. This launch schedules a background refresh for 12 hours later, at 8PM
12PM (noon) - user launches the app, views some content, then exits the app.
Does the scheduled refresh for 8PM still exist, or does the launch at noon invalidate that task, since the refresh could conceivably be handled during that noon launch?
Hopefully this is articulated clearly enough, but I'm trying to understand the specifics of background refresh behavior, since I don't want to run that refresh every time the app is opened. However, if opening the app invalidates scheduled refreshes, I will need to include logic that will reschedule the refresh accordingly.
I have widgets providing their timeline using the .atEnd reload policy, i.e.:
// AppIntentTimelineProvider:
return Timeline(entries: entries, policy: .atEnd)
// TimelineProvider
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
I can't seem to find any information on what happens after the end of the timeline. So, let's say I've got two days worth of entries, the dev docs for the reload policy say, "A policy that specifies that WidgetKit requests a new timeline after the last date in a timeline passes."
Great! But how does it request the new timeline? Does iOS launch my app in the background and simply re-run the timeline to generate another two days worth of entries? I doubt it.
I figure I need to implement some sort of background task, and the dev docs say how to do it with an Operation, but then I read that this is an old way of doing it? I've found some info online saying to use something like this, so this is what I've implemented:
let kBackgroundWidgetRefreshTask = "my.refresh.task.identifier" // This has been registered in the info.plist correctly
class SchedulingService {
static let shared = SchedulingService()
func registerBackgroundTasks() {
let isRegistered = BGTaskScheduler.shared.register(forTaskWithIdentifier: kBackgroundWidgetRefreshTask, using: nil) { task in
print("Background task is executing: \(task.identifier)") // This does print "true"
self.handleWidgetRefresh(task: task as! BGAppRefreshTask)
}
print("Is the background task registered? \(isRegistered)")
}
func scheduleWidgetRefresh() {
let request = BGAppRefreshTaskRequest(identifier: kBackgroundWidgetRefreshTask)
// Fetch no earlier than 1 hour from now - test, will be two days
request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60)
do {
try BGTaskScheduler.shared.submit(request)
print("Scheduled widget refresh for one hour from now")
} catch {
print("Could not schedule widget refresh: \(error)")
}
}
private func handleWidgetRefresh(task: BGAppRefreshTask) {
// Schedule a new refresh task
scheduleWidgetRefresh()
// Start refresh of the widget data
let refreshTask = Task {
do {
print("Going to refresh widgets")
try await self.backgroundRefreshWidgets()
task.setTaskCompleted(success: true)
} catch {
print("Could not refresh widgets: \(error)")
task.setTaskCompleted(success: false)
}
}
// Provide the background task with an expiration handler that cancels the operation
task.expirationHandler = {
refreshTask.cancel()
}
}
func backgroundRefreshWidgets() async throws {
print("backgroundRefreshWidgets() called")
definitelyRefreshWidgets()
}
}
As I've commented above, the line print("Background task is executing: \(task.identifier)") does print true so the task has been registered correctly.
I've put the app into the background and left it for hours and nothing is printed to the console. I've implemented a logger that writes to a file in the app container, but that doesn't get anything either.
So, is there something I'm misunderstanding? Should I change the reload policy to .after(date)? But what makes the timeline reload?
As a second but linked issue, my widgets have countdown timers on them and the entire timeline shows that every entry is correct, but the widgets on the Home Screen simply fail to refresh correctly.
For example, with timeline entries for every hour for the next two days from 6pm today (so, 7pm, 8pm...) every entry in the preview in Xcode shows the right countdown timer. However, if you put the widget on the Home Screen, after about five hours the timer shows 25:12:34 (for example).
No entry in the timeline preview ever shows more than 24 hours because the entires are every hour, and the one that shows a timer starting at 23:00:00 should never get to 24:00:00 as the next entry would kick in from 0:00:00, so it should never show more than 23:59:59 on the timer. It's like the 23:00:00 timer is just left to run for hours instead of being replaced by the next entry.
It's as though the widget isn't refreshing correctly and entries aren't loaded? Given this is the Simulator - and my development device - and both are set to Developer Mode so widget refresh budgets aren't an issue, why is this happening? How do you get widgets to refresh properly? The dev docs are not very helpful (neither is the Backyard Birds example Apple keep pushing).
Thanks!
Hi,
I'd like to develop an app which runs speech recognition even after going into background. I know I can accomplish this using audio background mode and the process the audio but I am not sure if this workaround would get accepted into App Store because of the processing limitations while in the background.
How can I accomplish this while still being compliant with Apples privacy policy and other restrictions?
Thanks,
Marek
Hello,
our application works with Core Data to save some datas about its activity.
We have background Tasks implemented and our app execution in background shows this error message in the Logs:
error: Failed to acquire background task assertion for task 'CoreData: Executing write request'.
Anyone could explain what this message means?
Could it be that NSManagedObjectContext changes might not be written?
I have a use case in my app where I need to call an API from the device every 30 minutes to verify the app state. In certain cases, the backend records the time of the last received API call or observes the absence of an API call for business logic, making it crucial that the call is executed at fixed intervals.
I am using React Native to build the app and the react-native-background-fetch package to create the background task. As I understand it, the package uses the Background Fetch API under the hood.
From what I have observed and read from other sources so far, there is no guarantee that the task will run every 30 minutes. Is there any way to make the background task more consistent, or is there a better approach to achieve this objective on iOS?
We have a device which is an appliance and we are developing a control interface app for macOS and iOS/iPadOS.
How can we set up our iOS application to grab information from a local network device while it is in the background in order to show notifications?
Communication between the Apple device and our device is via local networking and the device is designed to be used on networks without internet connections. On networks with internet connections we could forward events from the device, via a server and APNS push notifications, but that isn't valid here.
Events occur on our device and are forwarded to clients, who are subscribed to Server-Sent Events. On macOS this works well and the application can receive updates and show Notification Center notifications fine.
On iOS we are using a BGAppRefreshTaskRequest with time interval set to 1 minute, but it appears that we get scheduled only every few hours. This isn't very useful as notifications just arrive in batches rather than in a timely manner. All normal networking is closed when the app goes into the background, so we cannot keep the SSE request open.
Another idea which we haven't tried yet: Creating a new endpoint on the device which keeps the connection open until a notification arrives, then using background URLSession to poll on that endpoint. Would that work? It seems like a mis-use of the API perhaps?
I have an app, that when enters the background schedules a task to run. The earliest possible time value is set, as is the completion handler when the task eventually runs. It seems to run pretty reliably for the 1st few interations and then (from looking at the streaming Console logs), doesn't seem to reach a high CP score to execute next time around. eg
'......background.task:EDBC23' CurrentScore: 0.648418, ThresholdScore: 0.808034 DecisionToRun:0
looking at the previous entries before this, I can see the breakdown...
{name: Application Policy, policyWeight: 50.000, response: {0, 0.35}}
{name: Device Activity Policy, policyWeight: 5.000, response: {0, 0.50}}
], Decision: CP Score: 0.648418}
and I understand certain elements are outside of our control; however, is there a preferred method to get a background task (which ultimately runs an API call) to trigger consistently? The silent-push method has come up a few times - but of course, if the user disables / doesn't consent to push notifications, that fails
Any suggestions?
I'm implementing a timer feature and facing the issue that the live activity I'm starting just continues showing after the timer is complete.
The body of the live activity widget is more or less:
ActivityConfiguration(for: WhendyWidgetAttributes.self) { context in
VStack {
Text(
context.state.timerEndDate,
style: .timer
)
// if Date.now < timerEndTime { Text("Done") }
self.expandedView(state: context.state)
}
} …
Ideally I could get the activity to show something else when it is done but I don't know how to get it to re-evaluate it's body once the end time is reached.
I create the activity with
let activity = try ActivityKit.Activity.request(
attributes: attributes,
content: .init(
state: .init(timerEndDate: timerEndDate),
staleDate: timerEndDate
),
pushType: nil
)
Can I schedule the activity to do a refresh it's body (and reevaluating Date.now) once the timerEndDate is reached?
Considered Approaches
trying staleDate
However, the activity never shows that it has become stale. Would it be expected that it shows the stale-ness?
scheduling dismissal
I also thought about starting and immediately stopping the activity with a delayed dismissal, but unfortunately it seems this is limited to a 4 hour window, and I'd like longer timers too.
remote updates
I understand I could use remote notifications to update the live activity, but I'd really like to keep things local as all the functionality is locally plannable.
Background Tasks
I understand these don't run reliably or at a predictable time.
A Timer in the app that updates the content
I think this would only update the activity while the app is in foreground.
DeviceActivityReport presents statistics for a device: https://developer.apple.com/documentation/deviceactivity/deviceactivityreport
The problem: DeviceActivityReport can present statistics with a delay for a parent device (when DeviceActivityReport is presenting, the DeviceActivityReportExtension is called to process the statistics). One possible solution is to call DeviceActivityReport periodically throughout the day in a child device. However, the app will not be available all day. Is there any way to run DeviceActivityReport in the background?
I have tried the following approach, but it didn’t work (DeviceActivityReportExtension didnt call):
let hostingController: UIHostingController? = .init(rootView: DeviceActivityReport(context, filter: filter))
hostingController?.view.frame = .init(origin: .zero, size: .init(width: 100, height: 100))
hostingController?.beginAppearanceTransition(true, animated: false)
hostingController?.loadView()
hostingController?.viewDidLoad()
try? await Task.sleep(for: .seconds(0.5))
hostingController?.viewWillAppear(true)
hostingController?.viewWillLayoutSubviews()
try? await Task.sleep(for: .seconds(0.5))
hostingController?.viewDidAppear(true)
try? await Task.sleep(for: .seconds(0.5))
hostingController?.didMove(toParent: rootVC)
try? await Task.sleep(for: .seconds(0.5))
hostingController?.viewWillLayoutSubviews()
hostingController?.viewDidLayoutSubviews()
hostingController?.view.layoutIfNeeded()
hostingController?.view.layoutSubviews()
hostingController?.endAppearanceTransition()
Is there any way to run DeviceActivityReport in the background? (when app is not visible/closed). The main problem is call DeviceActivityReport
Hello Apple Developer Team,
I am facing an issue with remote notifications in my iOS app. When the app is in a terminated (kill) state, notifications are successfully received by the device, but none of the app's handlers (like _firebaseMessagingBackgroundHandler in Flutter) are invoked. This is impacting our ability to process silent notifications or perform background tasks reliably when the app is not running.
Steps to reproduce:
Send a remote notification with content-available: 1 in the payload.
Confirm the notification is received by the device while the app is in kill mode.
Observe that no background or foreground notification methods are triggered in the app.
Expected Behavior: The app should invoke the background handler to process the notification payload, even in a terminated state.
Observed Behavior: The notification is delivered to the device, but no app-level processing occurs because none of the methods are triggered.
Can you please confirm if this is the intended behavior due to iOS limitations, or if there is a configuration or alternative solution to allow background handlers to execute in such scenarios? Any guidance or clarification would be highly appreciated.
Thank you!
The application was initially written in Swift, and we released an update where the app was rewritten in Flutter. Currently, we are adding a widget natively written in SwiftUI to the Home screen. The widget updates are managed by BGTaskScheduler. In BGTaskScheduler, an API request is made to fetch the latest data. The data is then processed to calculate an average value, which is subsequently sent to UserDefaults. The widget displays data fetched from UserDefaults. The minimum update interval is set to 30 minutes.
When testing the widget updates through a build in Xcode, the widget updates as expected at the specified interval. However, when this build was provided to users via TestFlight, the widget does not update for them. Could this issue be related to TestFlight’s resource limitations? Is there any guarantee that releasing this version will ensure the widget updates correctly for users?
Hello, I have a some problem with background fetch. In my app I use background modes for fetch data and display on my home widget iPhone. Its working correct when I built app on my phone from Xcode but when I distribute my app on TestFlight my home widget not updating at all.
Help me understand if this issue is only due to TestFlight resources, or should I try releasing the app and hope that it will work in the release version?
We run simple iOS Swift code triggered by a remote notification:
UserDefaults.standard.set("key", forKey: "value")
It runs fine when the app is active or inactive, but when the device is closed/locked and the code is triggered, we see a warning in Xcode:
Couldn't write values for keys (
key
) in CFPrefsPlistSource<0x3018802d0> (Domain: com.example, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Path not accessible
Not updating lastKnownShmemState in CFPrefsPlistSource<0x3018802d0> (Domain: com.example, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): 767 -> 767
The issue is that there seems to be no way to catch that warning. The value is set, when it's re-read the value is correct. But the value is never written to disk, so after an app restart/update the value is gone, potentially has an old wrong value.
This code runs without any interruption, it's just showing the warning on iOS 17.7.1 on iPad:
UserDefaults.standard.set("key", forKey: "value")
UserDefaults.standard.synchronize()
print("value: \(UserDefaults.standard.string(forKey: "key"))")
Should there not be a way to catch this, so the code can act accordingly to the circumstances? It would be good to know inside the code that the value is not persisted. I would expect that an exception is generated somewhere which can be caught.
It seems .completeFileProtectionUntilFirstUserAuthentication enables files to be written to disk while the device is closed/locked, can something similar be used for UserDefaults.standard?
Hello everyone!
I'm having a problem with background tasks running in the foreground.
When a user enters the app, a background task is triggered. I've written some code to check if the app is in the foreground and to prevent the task from running, but it doesn't always work. Sometimes the task runs in the background as expected, but other times it runs in the foreground, as I mentioned earlier.
Could it be that I'm doing something wrong? Any suggestions would be appreciated.
here is code:
class BackgroundTaskService {
@Environment(\.scenePhase) var scenePhase
static let shared = BackgroundTaskService()
private init() {}
// MARK: - create task
func createCheckTask() {
let identifier = TaskIdentifier.check
BGTaskScheduler.shared.getPendingTaskRequests { requests in
if requests.contains(where: { $0.identifier == identifier.rawValue }) {
return
}
self.createByInterval(identifier: identifier.rawValue, interval: identifier.interval)
}
}
private func createByInterval(identifier: String, interval: TimeInterval) {
let request = BGProcessingTaskRequest(identifier: identifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: interval)
scheduleTask(request: request)
}
// MARK: submit task
private func scheduleTask(request: BGProcessingTaskRequest) {
do {
try BGTaskScheduler.shared.submit(request)
} catch {
// some actions with error
}
}
// MARK: background actions
func checkTask(task: BGProcessingTask) {
let today = Calendar.current.startOfDay(for: Date())
let lastExecutionDate = UserDefaults.standard.object(forKey: "lastCheckExecutionDate") as? Date ?? Date.distantPast
let notRunnedToday = !Calendar.current.isDate(today, inSameDayAs: lastExecutionDate)
guard notRunnedToday else {
task.setTaskCompleted(success: true)
createCheckTask()
return
}
if scenePhase == .background {
TaskActionStore.shared.getAction(for: task.identifier)?()
}
task.setTaskCompleted(success: true)
UserDefaults.standard.set(today, forKey: "lastCheckExecutionDate")
createCheckTask()
}
}
And in AppDelegate:
BGTaskScheduler.shared.register(forTaskWithIdentifier: "check", using: nil) { task in
guard let task = task as? BGProcessingTask else { return }
BackgroundTaskService.shared.checkNodeTask(task: task)
}
BackgroundTaskService.shared.createCheckTask()