I have been experimenting with the BGContinuedProcessingTask API recently (and published sample code for it https://github.com/infinitepower18/BGContinuedProcessingTaskDemo)
I have noticed that if I lock the phone, the code that runs as part of the task stops executing. My sample code simply updates the progress each second until it gets to 100, so it should be completed in 1 minute 40 seconds. However, after locking the phone and checking the lock screen a few seconds later the progress indicator was in the same position as before I locked it.
If I leave the phone locked for several minutes and check the lock screen the live activity says "Task failed".
I haven't seen anything in the documentation regarding execution of tasks while the phone is locked. So I'm a bit confused if I encountered an iOS bug here?
Processes & Concurrency
RSS for tagDiscover how the operating system manages multiple applications and processes simultaneously, ensuring smooth multitasking performance.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
The application is placed into the idle state. Subsequently, the device enters a sleep state.
While the device is in sleep, App start background task within the application successfully receives its expirationHandler callback.
App received the expiration callback and App called the end BGtask
OS did not released the Assertion.
Resulting in App getting terminated by the OS for exceeding the BG task
Apple Feedback- FB19192371
Hello,
I'm trying to adopt the new BGContinuedProcessingTask API, but I'm having a little trouble imagining how the API authors intended it be used. I saw the WWDC talk, but it lacked higher-level details about how to integrate this API, and I can't find a sample project.
I notice that we can list wildcard background task identifiers in our Info.plist files now, and it appears this is to be used with continued tasks - a user might start one video encoding, then while it is ongoing, enqueue another one from the same app, and these tasks would have identifiers such as "MyApp.VideoEncoding.ABCD" and "MyApp.VideoEncoding.EFGH" to distinguish them.
When it comes to implementing this, is the expectation that we:
a) Register a single handler for the wildcard pattern, which then figures out how to fulfil each request from the identifier of the passed-in task instance?
Or
b) Register a unique handler for each instance of the wildcard pattern? Since you can't unregister handlers, any resources captured by the handler would be leaked, so you'd need to make sure you only register immediately before submission - in other words register + submit should always be called as a pair.
Of course, I'd like to design my application to use this API as the authors intended it be used, but I'm just not entirely sure what that is. When I try to register a single handler for a wildcard pattern, the system rejects it at runtime (while allowing registrations for each instance of the pattern, indicating that at least my Info.plist is configured correctly). That points towards option B.
If it is option B, it's potentially worth calling that out in documentation - or even better, perhaps introduce a new call just for BGContinuedProcessingTask instead of the separate register + submit calls?
Thanks for your insight.
K
Aside: Also, it would be really nice if the handler closure would be async. Currently if you need to await on something, you need to launch an unstructured Task, but that causes issues since BGContinuedProcessingTask is not Sendable, so you can't pass it in to that Task to do things like update the title or mark the BGTask as complete.
Testing Environment:
iOS Version: 26.0 Beta 7
Xcode Version: 17.0 Beta 6
Device: iPhone 16 Pro
Description:
We are implementing the new BGContinuedProcessingTask API and are using the wildcard identifier notation as described in the official documentation. Our Info.plist is correctly configured with a permitted identifier pattern, such as com.our-bundle.export.*.
We then register a single launch handler for this exact wildcard pattern. We are performing this registration within a UIViewController, which is a supported pattern as BGContinuedProcessingTask is explicitly exempt from the "register before applicationDidFinishLaunching" requirement, according to the BGTaskScheduler.h header file. The register method correctly returns true, indicating the registration was successful.
However, when we then try to submit a task with a unique identifier that matches this pattern (e.g., com.our-bundle.export.UUID), the BGTaskScheduler.shared.submit() call throws an NSInternalInconsistencyException and terminates the app. The error reason is: 'No launch handler registered for task with identifier com.our-bundle.export.UUID'.
This indicates that the system is not correctly matching the specific, unique identifier from the submit call to the registered wildcard pattern handler. This behavior contradicts the official documentation.
Steps to Reproduce:
Create a new Xcode project.
In Signing & Capabilities, add "Background Modes" (with "Background processing" checked) and "Background GPU Access".
Add a permitted identifier (e.g., "com.company.test.*") to BGTaskSchedulerPermittedIdentifiers in Info.plist.
In a UIViewController's viewDidLoad, register a handler for the wildcard pattern. Check that the register method returns true.
Immediately after, try to submit a BGContinuedProcessingTaskRequest with a unique identifier that matches the pattern.
Expected Results:
The submit call should succeed without crashing, and the task should be scheduled.
Actual Results:
The app crashes immediately upon calling submit(). The console shows an uncaught NSInternalInconsistencyException with the reason: 'No launch handler registered for task with identifier com.company.test.UUID'.
Workaround:
The issue can be bypassed if we register a new handler for each unique identifier immediately before submitting a request with that same unique identifier. This strongly suggests the bug is in the system's wildcard pattern-matching logic.
Problem summary
I have a macOS helper app that is launched from a sandboxed main app. The helper:
has com.apple.security.app-sandbox = true and com.apple.security.inherit = true in its entitlements,
is signed and embedded inside the main app bundle (placed next to the main executable in Contents/MacOS),
reports entitlement_check = 1 (code signature contains sandbox entitlement, implemented via SecStaticCode… check),
sandbox_check(getpid(), NULL, 0) returns 1 (runtime sandbox enforcement present),
APP_SANDBOX_CONTAINER_ID environment variable is not present (0).
Despite that, Cocoa APIs return non-container home paths:
NSHomeDirectory() returns /Users/<me>/ (the real home).
[[NSFileManager defaultManager] URLsForDirectory:inDomains:] and
URLForDirectory:inDomain:appropriateForURL:create:error: return paths rooted at /Users/<me>/ (not under ~/Library/Containers/<app_id>/Data/...) — i.e. they look like non-sandboxed locations.
However, one important exception: URLForDirectory:... for NSItemReplacementDirectory (temporary/replacement items) does return a path under the helper's container (example: ~/Library/Containers/<app_id>/Data/tmp/TemporaryItems/NSIRD_<helper_name>_hfc1bZ).
This proves the sandbox is active for some FileManager APIs, yet standard directory lookups (Application Support, Documents, Caches, and NSHomeDirectory()) are not being redirected to the container.
What I expect
The helper (which inherits the sandbox and is clearly sandboxed) should get container-scoped paths from Cocoa’s FileManager APIs (Application Support, Documents, Caches), i.e. paths under the helper’s container: /Users/<me>/Library/Containers/<app_id>/Data/....
What I tried / diagnostics already gathered
Entitlements & code signature
codesign -d --entitlements :- /path/to/Helper.app/Contents/MacOS/Helper
# shows com.apple.security.app-sandbox = true and com.apple.security.inherit = true
Runtime checks (Objective-C++ inside helper):
extern "C" int sandbox_check(pid_t pid, const char *op, int flags);
NSLog(@"entitlement_check = %d", entitlement_check()); // SecStaticCode check
NSLog(@"env_variable_check = %d", (getenv("APP_SANDBOX_CONTAINER_ID") != NULL));
NSLog(@"runtime_sandbox_check = %d", sandbox_check(getpid(), nullptr, 0));
NSLog(@"NSHomeDirectory = %s", NSHomeDirectory());
NSArray *urls = [[NSFileManager defaultManager]
URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
NSLog(@"URLsForDirectory: %@", urls);
Observed output:
entitlement_check = 1
env_variable_check = 0
runtime_sandbox_check = 1
NSHomeDirectory = /Users/<me>
URLsForDirectory: ( "file:///Users/<me>/Library/Application%20Support/..." )
Temporary/replacement directory (evidence sandbox active for some APIs):
NSURL *tmpReplacement = [[NSFileManager defaultManager]
URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:&err];
NSLog(@"NSItemReplacementDirectory: %@", tmpReplacement.path);
Observed output (example):
/Users/<me>/Library/Containers/<app_id>/Data/tmp/TemporaryItems/NSIRD_<helper_name>_hfc1bZ
Other facts
Calls to NSHomeDirectory() and URLsForDirectory: are made after main() to avoid "too early" initialization problems.
Helper is placed in Contents/MacOS (not Contents/Library/LoginItems).
Helper is a non-GUI helper binary launched by the main app (not an XPC service).
macOS version: Sequoia 15.6
Questions
Why do NSHomeDirectory() and URLsForDirectory: return the real /Users/<me>/... paths in a helper process that is clearly sandboxed (entitlement + runtime enforcement), while NSItemReplacementDirectory returns a container-scoped temporary path?
Is this behavior related to how the helper is packaged or launched (e.g., placement in Contents/MacOS vs Contents/Library/LoginItems, or whether it is launched with posix_spawn/fork+exec vs other APIs)?
Are there additional entitlements or packaging rules required for a helper that inherits sandbox to have Cocoa directory APIs redirected to the container (for Application Support, Documents, Caches)?
*Thanks in advance — I can add any requested logs
I abandoned Mac development back around 10.4 when I departed Apple and am playing catch-up, trying to figure out how to register a privileged helper tool that can execute commands as root in the new world order. I am developing on 13.1 and since some of these APIs debuted in 13, I'm wondering if that's ultimately the root of my problem.
Starting off with the example code provided here:
https://developer.apple.com/documentation/servicemanagement/updating-your-app-package-installer-to-use-the-new-service-management-api
Following all build/run instructions in the README to the letter, I've not been successful in getting any part of it to work as documented. When I invoke the register command the test app briefly appears in System Settings for me to enable, but once I slide the switch over, it disappears. Subsequent attempts to invoke the register command are met only with the error message:
`Unable to register Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}
The app does not re-appear in System Settings on these subsequent invocations. When I invoke the status command the result mysteriously equates to SMAppService.Status.notFound.
The plist is in the right place with the right name and it is using the BundleProgram key exactly as supplied in the sample code project. The executable is also in the right place at Contents/Resources/SampleLaunchAgent relative to the app root.
The error messaging here is extremely disappointing and I'm not seeing any way for me to dig any further without access to the underlying Objective-C (which the Swift header docs reference almost exclusively, making it fairly clear that this was a... Swift... Port... [Pun intended]).
I'm developing a macOS console application that uses ODBC to connect to PostgreSQL. The application works fine when run normally, but fails to load the ODBC driver when debugging with LLDB(under root works fine as well).
Error Details
When running the application through LLDB, I get this sandbox denial in the system log (via log stream):
Error 0x0 0 0 kernel: (Sandbox) Sandbox: logd_helper(587) deny(1) file-read-data /opt/homebrew/lib/psqlodbcw.so
The application cannot access the PostgreSQL ODBC driver located at /opt/homebrew/lib/psqlodbcw.so(also tried copy to /usr/local/lib/...).
Environment
macOS Version: Latest Sequoia
LLDB: Using LLDB from Xcode 16.3 (/Applications/Xcode16.3.app/Contents/Developer/usr/bin/lldb)
ODBC Driver: PostgreSQL ODBC driver installed via Homebrew
Code Signing: Application is signed with Apple Development certificate
What is the recommended approach for debugging applications that need to load dynamic libraries?
Are there specific entitlements or configurations that would allow LLDB to access ODBC drivers during debugging sessions?
Any guidance would be greatly appreciated.
Thank you for any assistance!
The following code worked as expected on iOS 26 RC, but it no longer works on the official release of iOS 26.
Is there something I need to change in order to make it work on the official version?
Registration
BGTaskScheduler.shared.register(
forTaskWithIdentifier: taskIdentifier,
using: nil
) { task in
//////////////////////////////////////////////////////////////////////
// This closure is not called on the official release of iOS 26
//////////////////////////////////////////////////////////////////////
let task = task as! BGContinuedProcessingTask
var shouldContinue = true
task.expirationHandler = {
shouldContinue = false
}
task.progress.totalUnitCount = 100
task.progress.completedUnitCount = 0
while shouldContinue {
sleep(1)
task.progress.completedUnitCount += 1
task.updateTitle("\(task.progress.completedUnitCount) / \(task.progress.totalUnitCount)", subtitle: "any subtitle")
if task.progress.completedUnitCount == task.progress.totalUnitCount {
break
}
}
let completed = task.progress.completedUnitCount >= task.progress.totalUnitCount
if completed {
task.updateTitle("Completed", subtitle: "")
}
task.setTaskCompleted(success: completed)
}
Request
let request = BGContinuedProcessingTaskRequest(
identifier: taskIdentifier,
title: "any title",
subtitle: "any subtitle",
)
request.strategy = .queue
try BGTaskScheduler.shared.submit(request)
Sample project code:
https://github.com/HikaruSato/ExampleBackgroundProcess
I'm troubleshooting a crash I do not understand.
I have a queue called DataQueue which never has anything dispatched to it - it's the sample buffer delegate of an AVCaptureVideoDataOutput. It can call DispatchQueue.main.sync to do some work on the main thread.
It works fine no matter what we test, but has some crashes in the field that I need to fix. Here's it crashing:
AppleCameraDataDelegate.dataQueue
0 libsystem_kernel.dylib 0x7bdc __ulock_wait + 8
1 libdispatch.dylib 0x4a80 _dlock_wait + 52
2 libdispatch.dylib 0x486c _dispatch_thread_event_wait_slow$VARIANT$mp + 52
3 libdispatch.dylib 0x113d8 __DISPATCH_WAIT_FOR_QUEUE__ + 332
4 libdispatch.dylib 0x10ff0 _dispatch_sync_f_slow + 140
The main thread isn't doing something I asked it to, but appears to be busy:
Thread
0 libsystem_kernel.dylib 0x71a4 __psynch_cvwait + 8
1 libsystem_pthread.dylib 0x7fd8 _pthread_cond_wait$VARIANT$mp + 1232
2 grpc 0x2cb670 gpr_cv_wait + 131 (sync.cc:131)
3 grpc 0x119688 grpc_core::Executor::ThreadMain(void*) + 225 (executor.cc:225)
4 grpc 0x2e023c grpc_core::(anonymous namespace)::ThreadInternalsPosix::ThreadInternalsPosix(char const*, void (*)(void*), void*, bool*, grpc_core::Thread::Options const&)::'lambda'(void*)::__invoke(void*) + 146 (thd.cc:146)
5 libsystem_pthread.dylib 0x482c _pthread_start + 104
6 libsystem_pthread.dylib 0xcd8 thread_start + 8
Can anyone help me understand why this is a crash?
We have an application that sets a code signing requirement on a XPC connection between a File Provider extension and the main application. Only with a specific Developer ID certificate <DEVELOPER_ID_TEAM_IDENTIFIER> that designated requirement is not accepted and the application crashes with EXC_CRASH (SIGABRT) and the stacktrace
Thread 1 Crashed:: Dispatch queue: com.apple.root.default-qos
0 libsystem_kernel.dylib 0x19b556388 __pthread_kill + 8
1 libsystem_pthread.dylib 0x19b58f88c pthread_kill + 296
2 libsystem_c.dylib 0x19b498a3c abort + 124
3 libc++abi.dylib 0x19b545384 abort_message + 132
4 libc++abi.dylib 0x19b533cf4 demangling_terminate_handler() + 344
5 libobjc.A.dylib 0x19b1b8dd4 _objc_terminate() + 156
6 libc++abi.dylib 0x19b544698 std::__terminate(void (*)()) + 16
7 libc++abi.dylib 0x19b547c30 __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 88
8 libc++abi.dylib 0x19b547bd8 __cxa_throw + 92
9 libobjc.A.dylib 0x19b1aecf8 objc_exception_throw + 448
10 Foundation 0x19d5c3840 -[NSXPCConnection setCodeSigningRequirement:] + 140
11 libxpcfileprovider.dylib 0x301023048 NSXPCConnection.setCodeSigningRequirementFromTeamIdentifier(_:) + 1796
12 libxpcfileprovider.dylib 0x30101dc94 closure #1 in CallbackFileProviderManager.getFileProviderConnection(_:service:completionHandler:interruptionHandler:exportedObject:) + 1936
13 libxpcfileprovider.dylib 0x30101e110 thunk for @escaping @callee_guaranteed @Sendable (@guaranteed NSXPCConnection?, @guaranteed Error?) -> () + 80
14 Foundation 0x19d46c3a4 __72-[NSFileProviderService getFileProviderConnectionWithCompletionHandler:]_block_invoke_2.687 + 284
15 libdispatch.dylib 0x19b3d7b2c _dispatch_call_block_and_release + 32
16 libdispatch.dylib 0x19b3f185c _dispatch_client_callout + 16
17 libdispatch.dylib 0x19b40e490 + 32
18 libdispatch.dylib 0x19b3e9fa4 _dispatch_root_queue_drain + 736
19 libdispatch.dylib 0x19b3ea5d4 _dispatch_worker_thread2 + 156
20 libsystem_pthread.dylib 0x19b58be28 _pthread_wqthread + 232
21 libsystem_pthread.dylib 0x19b58ab74 start_wqthread + 8
The designated codesign requirement on the XPC connection is set to
anchor apple generic and certificate leaf[subject.OU] = <DEVELOPER_ID_TEAM_IDENTIFIER>"
We have verified the designated code sign requirement to be valid on both the main bundle and the embedded extension using:
codesign --verify -v -R '=anchor apple generic and certificate leaf[subject.OU] = "<DEVELOPER_ID_TEAM_IDENTIFIER>"' *.app
codesign --verify -v -R '=anchor apple generic and certificate leaf[subject.OU] = "<DEVELOPER_ID_TEAM_IDENTIFIER>"' *.app/Contents/PlugIns/*
In macOS 26 I noticed there is a section Menu Bar in System Settings which allows to toggle visibility of status items created with NSStatusItem. I'm assuming this is new, since I never noticed it before.
Currently my app has a menu item that allows toggling its status item, but now I wonder whether it should always create the status item and let the user control its visibility from System Settings. Theoretically, keeping this option inside the app could lead to confusion if the user has previously disabled the status item in System Settings, then perhaps forgot about it, and then tries to enable it inside the app, but apparently nothing happens because System Settings overrides the app setting. Should I remove the option inside the app?
This also makes me think of login items, which can be managed both in System Settings and inside the app via SMAppService. Some users ask why my app doesn't have a launch at login option, and I tell them that System Settings already offers that functionality. Since there is SMAppService I could offer an option inside the app that is kept in sync with System Settings, but I prefer to avoid duplicating functionality, particularly if it's something that is changed once by the user and then rarely (if ever) changed afterwards. But I wonder: why can login items be controlled by an app, and the status item cannot (at least I'm not aware of an API that allows to change the option in System Settings)? If the status item can be overridden in System Settings, why do login items behave differently?
In iOS Background Execution limits, I see this:
When the user ‘force quits’ an app by swiping up in the multitasking UI, iOS interprets that to mean that the user doesn’t want the app running at all. iOS also sets a flag that prevents the app from being launched in the background. That flag gets cleared when the user next launches the app manually.
However, I see that when I close an app on iPadOS 26 with the red X, the app doesn't appear in the multitasking UI. So are they treated as force closes and prevented from running background tasks?
I'm trying to understand how the API works to perform a function that can continue running if the user closes the app. For a very simple example, consider a function that increments a number on screen every second, counting from 1 to 100, reaching completion at 100. The user can stay in the app for 100s watching it work to completion, or the user can close the app say after 2s and do other things while watching it work to completion in the Live Activity.
To do this when the user taps a Start Counting button, you'd
1 Call BGTaskScheduler.shared.register(forTaskWithIdentifier:using:launchHandler:).
Question 1: Do I understand correctly, all of the logic to perform this counting operation would exist entirely in the launchHandler block (noting you could call another function you define passing it the task to be able to update its progress)? I am confused because the documentation states "The system runs the block of code for the launch handler when it launches the app in the background." but the app is already open in the foreground. This made me think this block is not going to be invoked until the user closes the app to inform you it's okay to continue processing in the background, but how would you know where to pick up. I want to confirm my thinking was wrong, that all the logic should be in this block from start to completion of the operation, and it's fine even if the app stays in the foreground the whole time.
2 Then you'd create a BGContinuedProcessingTaskRequest and set request.strategy = .fail for this example because you need it to start immediately per the user's explicit tap on the Start Counting button.
3 Call BGTaskScheduler.shared.submit(request).
Question 2: If the submit function throws an error, should you handle it by just performing the counting operation logic (call your function without passing a task)? I understand this can happen if for some reason the system couldn't immediately run it, like if there's already too many pending task requests. Seems you should not show an error message to the user, should still perform the request and just not support background continued processing for it (and perhaps consider showing a light warning "this operation can't be continued in the background so keep the app open"). Or should you still queue it up even though the user wants to start counting now? That leads to my next question
Question 3: In what scenario would you not want the operation to start immediately (the queue behavior which is the default), given the app is already in the foreground and the user requested some operation? I'm struggling to think of an example, like a button titled Compress Photos Whenever You Can, and it may start immediately or maybe it won't? While waiting for the launchHandler to be invoked, should the UI just show 0% progress or "Pending" until the system can get to this task in the queue? Struggling to understand the use cases here, why make the user wait to start processing when they might not even intend to close the app during the operation?
Thanks for any insights! As an aside, a sample project with a couple use cases would have been incredibly helpful to understand how the API is expected to be used.
Hello,
I have a few questions regarding the documentation here:
Can this method described in the article be built with Xcode 26 and run on iOS 26? Or is it restricted to run only on iOS 26, since AppExtensionPoint appears to be available starting from iOS 26?
Does this approach allow two apps under the same Team ID to communicate with each other?
Does this approach also allow two apps under different Team IDs to communicate with each other?
Is it mandatory to implement EXAppExtensionBrowserViewController and obtain user consent before using this method to exchange information?
In our implementation, we followed the documentation. Inside EXAppExtensionBrowserViewController, we were able to see the Generic Extension from another app and enabled the permission.
However, we still get the following error:
Failed to connect: Error Domain=NABUExtensionConnector Code=1
"No matching extension found"
UserInfo={NSLocalizedDescription=No matching extension found}
Could someone clarify whether this is expected behavior, or if we are missing an additional configuration step?
Thanks in advance!
Hello,
In a launched agent, I need to call into a third‑party library that may occasionally hang. At present, these calls are made from a separate thread, but if the thread hangs it cannot be terminated (pthread_cancel/pthread_kill are ineffective).
Would Apple recommend isolating this functionality in a separate process that can be force‑terminated if it becomes unresponsive, or is there a preferred approach for handling such cases in launched agents?
Can I use the system call fork() in launched agent?
Thank you in advance!
Hi, I have been recently debugging the BGContinuedProcessingTask API and encountered some of the following issues. I hope you can provide some answers:
First, let me explain my understanding of this API. I believe its purpose is to allow an app to trigger tasks that can be represented with progress indicators and require a certain amount of time to complete.
After entering the background, these tasks can continue to be completed through the BGContinuedProcessingTask, preventing the system from terminating them before they are finished.
In the launchHandler of the registration process, we only need to do a few things:
Determine whether the actual business processing is still ongoing.
Update the progress, title, and subtitle.
Handle the expirationHandler.
Set the task as completed.
Here are some issues I encountered during my debugging process:
After I called register and submit, the BGContinuedProcessingTask could not be triggered. The return values from my API calls were all normal.
I tried different device models, and some could trigger the task normally, such as the 15 Pro Max and 12 Pro Max. However, there were also some models, such as the 17 Pro, 15 Pro, and 15, that could not trigger the task properly. Moreover, there was no additional error information to help locate the issue.
The background task failed unexpectedly, but my app was still running normally. As I mentioned above, my launchHandler only retrieves the actual business status and updates it.
If a background task fails unexpectedly while the app is still running normally, it can mislead users and degrade the user experience of the app.
Others have also mentioned the issue of inconsistent behavior on devices that do not support Dynamic Island. On devices that support Dynamic Island,
when a task is triggered in the foreground, the app does not immediately display a pop-up notification within the app. However, on devices that do not support Dynamic Island,
the app directly displays a pop-up notification within the app, and this notification does not disappear when switching between different screens within the same app.
The user needs to actively swipe up to dismiss it. I think this experience is too intrusive for users. I would like to know whether this will be maintained in the future or if there is a plan to fix it.
On devices that do not support Dynamic Island, using the beta version 26.1 of the system,
if the system is in dark mode but the app triggers a business interface in white, the pop-up notification will have the same color as the current page, making it difficult to read the content inside the pop-up.
Users can actively stop background tasks by using the stop button, or the system can also stop tasks automatically when resources are insufficient or when a task is abnormal.
However, according to the current API, all these actions are triggered through the expirationHandler.
Currently, there is no way to distinguish whether the task was stopped by the user, by the system due to resource insufficiency, or due to an abnormal task.
I would like to know whether there will be more information provided in the future to help distinguish these different scenarios.
I believe that the user experience issues mentioned in points 2 and 3 are the most important. Please help to answer the questions and concerns above. Thank you!
i am trying to create a daemon with xpc for my app by referring to https://github.com/alienator88/HelperToolApp but i keep getting XPC remote proxy error: Couldn’t communicate with a helper application. All the identifiers all correct but the helper code is not reached.
On macOS 15.7.1 I'm trying to install an XPC service outside the app (Developer ID). It mostly seems to go ok, but when I set Launch Constraints on Responsible, AMFI complains of a violation, saying the service is responsible for itself, and fails to launch. Removing that constraint (or adding the service itself to the constraint) works fine.
The service is an optional download, and installed to /Users/Shared with a LaunchAgent specifying the MachService. The service is correctly launched and seems to pass all codesigning, notarization, and other checks, but the Responsible isn't set to the "calling" app.
Is this broken, or working as intended?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
XPC
Code Signing
Developer ID
Service Management
Issue:
Background downloads using the flutter_downloader package work perfectly in debug mode and release mode when run directly from Xcode (plugged in).
However, when I create an archive build and install the app separately (via TestFlight or direct IPA install), the background download stops working as soon as the app is minimized.
✅ What I’ve already done
Info.plist
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>fetch</string>
<string>processing</string>
<string>audio</string>
<string>push-to-talk</string>
</array>
AppDelegate.swift
import UIKit
import Flutter
import Firebase
import flutter_downloader
import BackgroundTasks
@main
@objc class AppDelegate: FlutterAppDelegate {
static let backgroundChannel = "com.example.app/background_service"
private var backgroundCompletionHandler: (() -> Void)?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
}
if #available(iOS 13.0, *) {
registerBackgroundTask()
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@available(iOS 13.0, *)
private func registerBackgroundTask() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.process_download_queue",
using: nil
) { [weak self] task in
guard let self = self else { return }
self.handleDownloadQueueTask(task: task as! BGProcessingTask)
}
}
@available(iOS 13.0, *)
private func handleDownloadQueueTask(task: BGProcessingTask) {
scheduleNextDownloadTask()
let headlessEngine = FlutterEngine(name: "BackgroundTaskEngine", project: nil, allowHeadlessExecution: true)
headlessEngine.run()
let channel = FlutterMethodChannel(
name: AppDelegate.backgroundChannel,
binaryMessenger: headlessEngine.binaryMessenger
)
task.expirationHandler = {
channel.invokeMethod("backgroundTaskExpired", arguments: nil)
}
channel.invokeMethod("processNextInBackground", arguments: nil) { result in
task.setTaskCompleted(success: (result as? Bool) ?? false)
}
}
override func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
self.backgroundCompletionHandler = completionHandler
super.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
override func applicationDidEnterBackground(_ application: UIApplication) {
if #available(iOS 13.0, *) {
scheduleNextDownloadTask()
}
}
@available(iOS 10.0, *)
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
if #available(iOS 14.0, *) {
completionHandler([.list, .banner, .badge, .sound])
} else {
completionHandler([.alert, .badge, .sound])
}
}
@available(iOS 10.0, *)
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
completionHandler()
}
}
// MARK: - Helper
@available(iOS 13.0, *)
func scheduleNextDownloadTask() {
let request = BGProcessingTaskRequest(identifier: "com.example.app.process_download_queue")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = false
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
print("BGTask: Download queue processing task scheduled successfully.")
} catch {
print("BGTask: Could not schedule download queue task: \(error)")
}
}
private func registerPlugins(registry: FlutterPluginRegistry) {
if !registry.hasPlugin("FlutterDownloaderPlugin") {
FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!)
}
}
🧩 Observations
Background download works correctly when:
The app is plugged in and run via Xcode (release/debug)
It stops working when:
The app is installed from an archived build (IPA/TestFlight) and minimized
All entitlements and background modes are properly added.
Provisioning profile includes required background modes.
❓Question
Is there any known limitation or signing difference between Xcode run and archived release builds that could cause URLSession background tasks not to trigger?
Has anyone faced a similar issue when using flutter_downloader on iOS 13+ with BGTaskScheduler or URLSession background configuration?
Any help or working setup example for production/TestFlight would be appreciated.
For example, let’s propose an XPC service that can connect to websites. Suppose that I want to connect to Apple.com, microsoft.com, and ibm.com. Can 3 service objects be made between the service and client? Or does the service have to return an ID for each web connection, with the client needing to specify which connection ID along with a command?