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?
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
Created
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.
Some users of my Mac app are complaining of redrawing delays. Based on what I see in logs, my GCD timer event handlers are not being run in a timely manner although the runloop is still pumping events: sometimes 500ms pass before a 15ms timer runs. During this time, many keypresses are routed through -[NSApplication sendEvent:], which is how I know it's not locked up in synchronous code.
This issue has not been reported in older versions of macOS.
I start the timer like this:
_gcdUpdateTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_gcdUpdateTimer,
dispatch_time(DISPATCH_TIME_NOW, period * NSEC_PER_SEC),
period * NSEC_PER_SEC,
0.0005 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_gcdUpdateTimer, ^{
…redraw…
});
I was stuck on a long train journey this weekend, so I thought I’d use that time to write up the process for installing a launchd daemon using SMAppService. This involves a number of deliberate steps and, while the overall process isn’t too hard — it’s certainly a lot better than with the older SMJobBless — it’s easy to accidentally stray from the path and get very confused.
If you have questions or comments, start a new thread in the App & System Services > Processes & Concurrency subtopic and tag it with Service Management.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Getting Started with SMAppService
This post explains how to use SMAppService to install a launchd daemon. I tested these instructions using Xcode 26.0 on macOS 15.6.1. Things are likely to be slightly different with different Xcode and macOS versions.
Create the container app target
To start, I created a new project:
I choose File > New > Project.
In the template picker, I chose macOS > App.
In options page, I set the Product Name field to SMAppServiceTest [1].
And I selected my team in the Team popup.
And I verified that the Organization Identifier was set to com.example.apple-samplecode, the standard for Apple sample code [1].
I selected SwiftUI in the Interface popup. There’s no requirement to use SwiftUI here; I chose it because that’s what I generally use these days.
And None in the Testing System popup.
And None in the Storage popup.
I then completed the new project workflow.
I configured basic settings on the project:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the SMAppServiceTest target.
At the top I selected Signing & Capabilities.
In the Signing section, I made sure that “Automatically manage signing” was checked.
And that my team was selected in the Team popup.
And that the bundle ID of the app ended up as com.example.apple-samplecode.SMAppServiceTest.
Still in the Signing & Capabilities tab, I removed the App Sandbox section.
Note It’s possible to use SMAppService to install a daemon from a sandboxed app, but in that case the daemon also has to be sandboxed. That complicates things, so I’m disabling the sandbox for the moment. See Enable App Sandbox, below, for more on this.
Next I tweaked some settings to make it easier to keep track of which target is which:
At the top, I selected the Build Settings tab.
I changed the Product Name build setting from $(TARGET_NAME) to SMAppServiceTest.
On the left, I renamed the target to App.
I chose Product > Scheme > Manage Schemes.
In the resulting sheet, I renamed the scheme from SMAppServiceTest to App, just to keep things in sync.
[1] You are free to choose your own value, of course. However, those values affect other values later in the process, so I’m giving the specific values I used so that you can see how everything lines up.
Create the daemon target
I then created a daemon target:
I chose File > New > Target.
In the template picker, I chose macOS > Command Line Tool.
In the options page, I set the Product Name field to Daemon.
And I selected my team in the Team popup.
And I verified that the Organization Identifier was set to com.example.apple-samplecode, the standard for Apple sample code.
I selected Swift in the Language popup.
And verified that SMAppServiceTest was set in the Project popup.
I clicked Finish.
I configured basic settings on the target:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the Daemon target.
At the top I selected Signing & Capabilities.
In the Signing section, I made sure that “Automatically manage signing” was checked.
And that my team was selected in the Team popup.
Note The Bundle Identifier field is blank, and that’s fine. There are cases where you want to give a daemon a bundle identifier, but it’s not necessary in this case.
Next I tweaked some settings to make it easier to keep track of which target is which:
At the top, I selected the Build Settings tab.
I changed the Product Name build setting from $(TARGET_NAME) to SMAppServiceTest-Daemon.
I forced the Enable Debug Dylib Support to No.
IMPORTANT To set it to No, you first have to set it to Yes and then set it back to No.
I edited Daemon/swift.swift to look like this:
import Foundation
import os.log
let log = Logger(subsystem: "com.example.apple-samplecode.SMAppServiceTest", category: "daemon")
func main() {
log.log("Hello Cruel World!")
dispatchMain()
}
main()
This just logs a ‘first light’ log message and parks [1] the main thread in dispatchMain().
Note For more about first light log points, see Debugging a Network Extension Provider.
[1] Technically the main thread terminates in this case, but I say “parks” because that’s easier to understand (-:
Test the daemon executable
I selected the Daemon scheme and chose Product > Run. The program ran, logging its first light log entry, and then started waiting indefinitely.
Note Weirdly, in some cases the first time I ran the program I couldn’t see its log output. I had to stop and re-run it. I’m not sure what that’s about.
I chose Product > Stop to stop it. I then switched back the App scheme.
Embed the daemon in the app
I added a build phase to embed the daemon executable into app:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the App target.
At the top I selected Build Phases.
I added a new copy files build phase.
I renamed it to Embed Helper Tools.
I set its Destination popup to Executables.
I clicked the add (+) button under the list and selected SMAppServiceTest-Daemon.
I made sure that Code Sign on Copy was checked for that.
I then created a launchd property list file for the daemon:
In the Project navigator, I selected SMAppServiceTestApp.swift.
I chose Product > New > File from Template.
I selected the Property List template.
In the save sheet, I named the file com.example.apple-samplecode.SMAppServiceTest-Daemon.plist.
And made sure that the Group popup was set to SMAppServiceTest.
And that only the App target was checked in the Targets list.
I clicked Create to create the file.
In the property list editor, I added two properties:
Label, with a string value of com.example.apple-samplecode.SMAppServiceTest-Daemon
BundleProgram, with a string value of Contents/MacOS/SMAppServiceTest-Daemon
I added a build phase to copy that property list into app:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the App target.
At the top I selected Build Phases.
I added a new copy files build phase.
I renamed it to Copy LaunchDaemons Property Lists.
I set its Destination popup to Wrapper.
And set the Subpath field to Contents/Library/LaunchDaemons.
I disclosed the contents of the Copy Bundle Resources build phase.
I dragged com.example.apple-samplecode.SMAppServiceTest-Daemon.plist from the Copy Bundle Resources build phase to the new Copy LaunchDaemons Property Lists build phase.
I made sure that Code Sign on Copy was unchecked.
Register and unregister the daemon
In the Project navigator, I selected ContentView.swift and added the following to the imports section:
import os.log
import ServiceManagement
I then added this global variable:
let log = Logger(subsystem: "com.example.apple-samplecode.SMAppServiceTest", category: "app")
Finally, I added this code to the VStack:
Button("Register") {
do {
log.log("will register")
let service = SMAppService.daemon(plistName: "com.example.apple-samplecode.SMAppServiceTest-Daemon.plist")
try service.register()
log.log("did register")
} catch let error as NSError {
log.log("did not register, \(error.domain, privacy: .public) / \(error.code)")
}
}
Button("Unregister") {
do {
log.log("will unregister")
let service = SMAppService.daemon(plistName: "com.example.apple-samplecode.SMAppServiceTest-Daemon.plist")
try service.unregister()
log.log("did unregister")
} catch let error as NSError {
log.log("did not unregister, \(error.domain, privacy: .public) / \(error.code)")
}
}
IMPORTANT None of this is code is structured as I would structure a real app. Rather, this is the absolutely minimal code needed to demonstrate this API.
Check the app structure
I chose Product > Build and verified that everything built OK. I then verified that the app’s was structured correctly:
I then choose Product > Show Build Folder in Finder.
I opened a Terminal window for that folder.
In Terminal, I changed into the Products/Debug directory and dumped the structure of the app:
% cd "Products/Debug"
% find "SMAppServiceTest.app"
SMAppServiceTest.app
SMAppServiceTest.app/Contents
SMAppServiceTest.app/Contents/_CodeSignature
SMAppServiceTest.app/Contents/_CodeSignature/CodeResources
SMAppServiceTest.app/Contents/MacOS
SMAppServiceTest.app/Contents/MacOS/SMAppServiceTest.debug.dylib
SMAppServiceTest.app/Contents/MacOS/SMAppServiceTest
SMAppServiceTest.app/Contents/MacOS/__preview.dylib
SMAppServiceTest.app/Contents/MacOS/SMAppServiceTest-Daemon
SMAppServiceTest.app/Contents/Resources
SMAppServiceTest.app/Contents/Library
SMAppServiceTest.app/Contents/Library/LaunchDaemons
SMAppServiceTest.app/Contents/Library/LaunchDaemons/com.example.apple-samplecode.SMAppServiceTest-Daemon.plist
SMAppServiceTest.app/Contents/Info.plist
SMAppServiceTest.app/Contents/PkgInfo
There are a few things to note here:
The com.example.apple-samplecode.SMAppServiceTest-Daemon.plist property list is in Contents/Library/LaunchDaemons.
The daemon executable is at Contents/MacOS/SMAppServiceTest-Daemon.
The app is still built as debug dynamic library (SMAppServiceTest.debug.dylib) but the daemon is not.
Test registration
I chose Product > Run. In the app I clicked the Register button. The program logged:
will register
did not register, SMAppServiceErrorDomain / 1
Error 1 indicates that installing a daemon hasn’t been approved by the user. The system also presented a notification:
Background Items Added
“SMAppServiceTest” added items that can
run in the background for all users. Do you
want to allow this?
Options > Allow
> Don’t Allow
I chose Allow and authenticated the configuration change.
In Terminal, I verified that the launchd daemon was loaded:
% sudo launchctl list com.example.apple-samplecode.SMAppServiceTest-Daemon
{
"LimitLoadToSessionType" = "System";
"Label" = "com.example.apple-samplecode.SMAppServiceTest-Daemon";
"OnDemand" = true;
"LastExitStatus" = 0;
"Program" = "Contents/MacOS/SMAppServiceTest-Daemon";
};
IMPORTANT Use sudo to target the global launchd context. If you omit this you end up targeting the launchd context in which Terminal is running, a GUI login context, and you won't find any launchd daemons there.
I started monitoring the system log:
I launched the Console app.
I pasted subsystem:com.example.apple-samplecode.SMAppServiceTest into the search box.
I clicked “Start streaming”.
Back in Terminal, I started the daemon:
% sudo launchctl start com.example.apple-samplecode.SMAppServiceTest-Daemon
In Console, I saw it log its first light log point:
type: default
time: 17:42:20.626447+0100
process: SMAppServiceTest-Daemon
subsystem: com.example.apple-samplecode.SMAppServiceTest
category: daemon
message: Hello Cruel World!
Note I’m starting the daemon manually because my goal here is to show how to use SMAppService, not how to use XPC to talk to a daemon. For general advice about XPC, see XPC Resources.
Clean up
Back in the app, I clicked Unregister. The program logged:
will unregister
did unregister
In Terminal, I confirmed that the launchd daemon was unloaded:
% sudo launchctl list com.example.apple-samplecode.SMAppServiceTest-Daemon
Could not find service "com.example.apple-samplecode.SMAppServiceTest-Daemon" in domain for system
Note This doesn’t clean up completely. The system remembers your response to the Background Items Added notification, so the next time you run the app and register your daemon it will be immediately available. To reset that state, run the sfltool with the resetbtm subcommand.
Install an Agent Rather Than a Daemon
The above process shows how to install a launchd daemon. Tweaking this to install a launchd agent is easy. There are only two required changes:
In the Copy Launch Daemon Plists copy files build phase, set the Subpath field to Contents/Library/LaunchAgents.
In ContentView.swift, change the two SMAppService.daemon(plistName:) calls to SMAppService.agent(plistName:).
There are a bunch of other changes you should make, like renaming everything from daemon to agent, but those aren’t required to get your agent working.
Enable App Sandbox
In some cases you might want to sandbox the launchd job (the term job to refer to either a daemon or an agent.) This most commonly crops up with App Store apps, where the app itself must be sandboxed. If the app wants to install a launchd agent, that agent must also be sandboxed. However, there are actually four combinations, of which three are supported:
App Sandboxed | Job Sandboxed | Supported
------------- | ------------- | ---------
no | no | yes
no | yes | yes
yes | no | no [1]
yes | yes | yes
There are also two ways to sandbox the job:
Continue to use a macOS > Command Line Tool target for the launchd job.
Use an macOS > App target for the launchd job.
In the first approach you have to use some low-level build settings to enable the App Sandbox. Specifically, you must assign the program a bundle ID and then embed an Info.plist into the executable via the Create Info.plist Section in Binary build setting.
In the second approach you can use the standard Signing & Capabilities editor to give the job a bundle ID and enable the App Sandbox, but you have to adjust the BundleProgram property to account for the app-like wrapper.
IMPORTANT The second approach is required if your launchd job uses restricted entitlements, that is, entitlements that must be authorised by a provisioning profile. In that case you need an app-like wrapper to give you a place to store the provisioning profile. For more on this idea, see Signing a daemon with a restricted entitlement.
For more background on how provisioning profiles authorise the use of entitlements, see TN3125 Inside Code Signing: Provisioning Profiles.
On balance, the second approach is the probably the best option for most developers.
[1] When SMAppService was introduced it was possible to install a non-sandboxed daemon from a sandboxed app. That option is blocked by macOS 14.2 and later.
Hello,
An application I am working on would like to schedule push notifications for a medication reminder app. I am trying to use BGTaskScheduler to wake up periodically and submit the notifications based on the user's medication schedule.
I set up the task registration in my AppDelegate's didFinishLaunchingWithOptions method:
BGTaskScheduler.shared.register(
forTaskWithIdentifier: backgroundTaskIdentifier,
using: nil) { task in
self.scheduleNotifications()
task.setTaskCompleted(success: true)
self.scheduleAppRefresh()
}
scheduleAppRefresh()
I then schedule the task using:
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: backgroundTaskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 1)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
}
}
In my testing, I can see the background task getting called once, but if I do not launch the application during the day. The background task does not get called the next day.
Is there something else I need to add to get repeated calls from the BGTaskScheduler?
Thank You,
JR
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Background Tasks
User Notifications
Started a new X-Code Project after updating to 26.0.1 and realized that I get an error when trying to mark a class as ObservableObject => "Class XYZ does not conform to Protocol 'ObservableObject'.
Strange behaviour, because at old projects the code is working even though the build options are the same and other settings like iOS version in Target are the same.
There must be something chaged under the hood of XCode? I have to import Combine now, before I could write my class, e.g. CoreData Datamanager: ObservableObject only using CoreData.
Hi,
We are running into issues with iOS app prewarming, where the system launches our app before the user has entered their passcode.
In our case, the app stores flags, counters, and session data in UserDefaults and the Keychain. During prewarm launches:
UserDefaults only returns default values (nil, 0, false). We have no way of knowing whether this information is valid or just a placeholder caused by prewarming.
Keychain items with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly are inaccessible, which can lead to broken business logic (the app can assume no session exists).
No special launch options or environment variables appear to be set.
We can reproduce this 100% of the time by starting a Live Activity in the app before reboot.
Here’s an example of the workaround we tried, following older recommendations:
__attribute__((constructor))
static void ModuleInitializer(void) {
char* isPrewarm = getenv("ActivePrewarm");
if (isPrewarm != NULL && isPrewarm[0] == '1') {
exit(0); // prevent prewarm launch from proceeding
}
}
On iOS 16+, the ActivePrewarm environment variable doesn’t seem to exist anymore (though older docs and SDKs such as Sentry reference it).
We also tried listening for UIApplication.protectedDataDidBecomeAvailableNotification, but this is not specific to prewarming (it also fires when the device gets unlocked) and can cause watchdog termination if we delay work too long.
Questions:
Is there a supported way to opt out of app prewarming?
What is the correct way to detect when an app is being prewarmed?
Is the ActivePrewarm environment variable still supported in iOS 16+?
Ideally, the UserDefaults API itself should indicate whether it is returning valid stored values or defaults due to the app being launched in a prewarm session.
We understand opting out may impact performance, but data security and integrity are our priority. Any guidance would be greatly appreciated.
Hello,
I'm running into an issue with a complex macOS application (non-AppStore) structure involving an unsandboxed system daemon and a sandboxed SSO Extension attempting to communicate via XPC Mach service.
The macOS app is composed of three main components:
Main App: unsandboxed, standard macOS application.
System Daemon: unsandboxed executable installed with a .plist to /Library/LaunchDaemons/ and loaded by launchd. It exposes an XPC Mach Service.
SSO Extension: a sandboxed Authentication Services Extension (ASAuthorizationProviderExtension).
Main App to System Daemon communication works perfectly. The unsandboxed main app can successfully create and use an XPC connection to the System Daemon's Mach service.
But SSO Extension cannot establish an XPC connection to the System Daemon's Mach service, despite using the recommended temporary exception entitlement. I have added the following entitlement to the SSO Extension's entitlements file:
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>my.xpc.service.system.daemon</string>
</array>
(The name my.xpc.service.system.daemon is the exact name registered by the System Daemon in its Launch Daemon plist's MachServices dictionary.)
When the SSO Extension attempts to create the connection, the following log output is generated:
default 08:11:58.531567-0700 SSOExtension [0x13f19b090] activating connection: mach=true listener=false peer=false name=my.xpc.service.system.daemon
default 08:11:58.532150-0700 smd [0xb100d8140] activating connection: mach=false listener=false peer=true name=com.apple.xpc.smd.peer[1575].0xb100d8140
error 08:11:58.532613-0700 smd Item real path failed. Maybe the item has been deleted?
error 08:11:58.532711-0700 SSOExtension Unable to find service status () error: 22
The error Unable to find service status () error: 22. Error code 22 typically translates to EINVAL (Invalid argument), but in this context, it seems related to the system's ability to find and activate the service for the sandboxed process.
Questions:
Is the com.apple.security.temporary-exception.mach-lookup.global-name entitlement sufficient for a sandboxed SSO Extension to look up a system-wide Launch Daemon Mach service, or are there additional restrictions or required entitlements for extensions?
The smd log output Item real path failed. Maybe the item has been deleted? seems concerning. Since the unsandboxed main app can connect, this suggests the service is running and registered. Could this error indicate a sandbox permission issue preventing smd from verifying the path for the sandboxed process?
Are there specific sandboxing requirements for Mach service names when communicating from an Extension versus a main application?
Any guidance on how a sandboxed SSO Extension can reliably connect to an unsandboxed, non-app-group-related system daemon via XPC Mach service would be greatly appreciated!
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!
What’s the recommended way to recursively walk through a directory tree using File Coordination? From what I understand, coordinating a read of a directory only performs a “shallow” lock; this would mean that I’d need to implement the recursive walk myself rather than use FileManager.enumerator(at:includingPropertiesForKeys:options:errorHandler:) plus a single NSFileCoordinator.coordinate(with:queue:byAccessor:) call.
I’m trying to extract information from all files of a particular type, so I think using NSFileCoordinator.ReadingOptions.immediatelyAvailableMetadataOnly on each file before acquiring a full read lock on it (if it’s the right file type) would make sense. Am I on the right track?
This comes up over and over, here on the forums and elsewhere, so I thought I’d post my take on it. If you have questions or comments, start a new thread here on the forums. Put it in the App & System Services > Processes & Concurrency subtopic and tag it with Concurrency.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Waiting for an Async Result in a Synchronous Function
On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function.
Lemme say that again, with emphasis…
On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function.
This post dives into the details of this reality.
Prime Offender
Imagine you have an asynchronous function and you want to call it from a synchronous function:
func someAsynchronous(input: Int, completionHandler: @escaping @Sendable (_ output: Int) -> Void) {
… processes `input` asynchronously …
… when its done, calls the completion handler with the result …
}
func mySynchronous(input: Int) -> Int {
… calls `someAsynchronous(…)` …
… waits for it to finish …
… results the result …
}
There’s no good way to achieve this goal on Apple platforms. Every approach you might try has fundamental problems.
A common approach is to do this working using a Dispatch semaphore:
func mySynchronous(input: Int) -> Int {
fatalError("DO NOT WRITE CODE LIKE THIS")
let sem = DispatchSemaphore(value: 0)
var result: Int? = nil
someAsynchronous(input: input) { output in
result = output
sem.signal()
}
sem.wait()
return result!
}
Note This code produces a warning in the Swift 5 language mode which turns into an error in the Swift 6 language mode. You can suppress that warning with, say, a Mutex. I didn’t do that here because I’m focused on a more fundamental issue here.
This code works, up to a point. But it has unavoidable problems, ones that don’t show up in a basic test but can show up in the real world. The two biggest ones are:
Priority inversion
Thread pools
I’ll cover each in turn.
Priority Inversion
Apple platforms have a mechanism that helps to prevent priority inversion by boosting the priority of a thread if it holds a resource that’s needed by a higher-priority thread. The code above defeats that mechanism because there’s no way for the system to know that the threads running the work started by someAsynchronous(…) are being waited on by the thread blocked in mySynchronous(…). So if that blocked thread has a high-priority, the system can’t boost the priority of the threads doing the work.
This problem usually manifests in your app failing to meet real-time goals. An obvious example of this is scrolling. If you call mySynchronous(…) from the main thread, it might end up waiting longer than it should, resulting in noticeable hitches in the scrolling.
Threads Pools
A synchronous function, like mySynchronous(…) in the example above, can be called by any thread. If the thread is part of a thread pool, it consumes a valuable resource — that is, a thread from the pool — for a long period of time. The raises the possibility of thread exhaustion, that is, where the pool runs out of threads.
There are two common thread pools on Apple platforms:
Dispatch
Swift concurrency
These respond to this issue in different ways, both of which can cause you problems.
Dispatch can choose to over-commit, that is, start a new worker thread to get work done while you’re hogging its existing worker threads. This causes two problems:
It can lead to thread explosion, where Dispatch starts dozens and dozens of threads, which all end up blocked. This is a huge waste of resources, notably memory.
Dispatch has an hard limit to how many worker threads it will create. If you cause it to over-commit too much, you’ll eventually hit that limit, putting you in the thread exhaustion state.
In contrast, Swift concurrency’s thread pool doesn’t over-commit. It typically has one thread per CPU core. If you block one of those threads in code like mySynchronous(…), you limit its ability to get work done. If you do it too much, you end up in the thread exhaustion state.
WARNING Thread exhaustion may seem like just a performance problem, but that’s not the case. It’s possible for thread exhaustion to lead to a deadlock, which blocks all thread pool work in your process forever.
There’s a trade-off here. Swift concurrency doesn’t over-commit, so it can’t suffer from thread explosion but is more likely deadlock, and vice versa for Dispatch.
Bargaining
Code like the mySynchronous(…) function shown above is fundamentally problematic. I hope that the above has got you past the denial stage of this analysis. Now let’s discuss your bargaining options (-:
Most folks don’t set out to write code like mySynchronous(…). Rather, they’re working on an existing codebase and they get to a point where they have to synchronously wait for an asynchronous result. At that point they have the choice of writing code like this or doing a major refactor.
For example, imagine you’re calling mySynchronous(…) from the main thread in order to update a view. You could go down the problematic path, or you could refactor your code so that:
The current value is always available to the main thread.
The asynchronous code updates that value in an observable way.
The main thread code responds to that notification by updating the view from the current value.
This refactoring may or may not be feasible given your product’s current architecture and timeline. And if that’s the case, you might end up deploying code like mySynchronous(…). All engineering is about trade-offs. However, don’t fool yourself into thinking that this code is correct. Rather, make a note to revisit this choice in the future.
Async to Async
Finally, I want to clarify that the above is about synchronous functions. If you have a Swift async function, there is a good path forward. For example:
func mySwiftAsync(input: Int) async -> Int {
let result = await withCheckedContinuation { continuation in
someAsynchronous(input: input) { output in
continuation.resume(returning: output)
}
}
return result
}
This looks like it’s blocking the current thread waiting for the result, but that’s not what happens under the covers. Rather, the Swift concurrency worker thread that calls mySwiftAsync(…) will return to the thread pool at the await. Later, when someAsynchronous(…) calls the completion handler and you resume the continuation, Swift will grab a worker thread from the pool to continue running mySwiftAsync(…).
This is absolutely normal and doesn’t cause the sorts of problems you see with mySynchronous(…).
IMPORTANT To keep things simple I didn’t implement cancellation in mySwiftAsync(…). In a real product it’s important to support cancellation in code like this. See the withTaskCancellationHandler(operation:onCancel:isolation:) function for the details.
Hi,
I am programming in C and would like to use Grand Central Dispatch for parallel computing (I mostly do physics based simulations). I remember there used to be example codes provided by Apple, but can't find those now. Instead I get the plain documentation. May anyone point me to the correct resources? It will be greatly appreciated. Thanks ☺.
I have several combine pipelines in my watch and iPhone app. While background tasks on the iPhone work correctly (the combine pipelines all activate), on the watch the pipelines do not get activated. I have an internal log reporting that data is being fed to the sources but is not propagating to the sinks.
Thoughts?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
watchOS
Combine
Background Tasks
Hello! We are in the progress of migrating a large Swift 5.10 legacy code base over to use Swift 6.0 with Strict Concurrency checking.
We have already stumbled across a few weird edge cases where the "guaranteed" @MainActor isolation is violated (such as with @objc #selector methods used with NotificationCenter).
However, we recently found a new scenario where our app crashes accessing main actor isolated state on a background thread, and it was surprising that the compiler couldn't warn us.
Minimal reproducible example:
class ViewController: UIViewController {
var isolatedStateString = "Some main actor isolated state"
override func viewDidLoad() {
exampleMethod()
}
/// Note: A `@MainActor` isolated method in a `@MainActor` isolated class.
func exampleMethod() {
testAsyncMethod() { [weak self] in
// !!! Crash !!!
MainActor.assertIsolated()
// This callback inherits @MainActor from the class definition, but it is called on a background thread.
// It is an error to mutate main actor isolated state off the main thread...
self?.isolatedStateString = "Let me mutate my isolated state"
}
}
func testAsyncMethod(completionHandler: (@escaping () -> Void)) {
let group = DispatchGroup()
let queue = DispatchQueue.global()
// The compiler is totally fine with calling this on a background thread.
group.notify(queue: queue) {
completionHandler()
}
// The below code at least gives us a compiler warning to add `@Sendable` to our closure argument, which is helpful.
// DispatchQueue.global().async {
// completionHandler()
// }
}
}
The problem:
In the above code, the completionHandler implementation inherits main actor isolation from the UIViewController class.
However, when we call exampleMethod(), we crash because the completionHandler is called on a background thread via the DispatchGroup.notify(queue:).
If were to instead use DispatchQueue.global().async (snippet at the bottom of the sample), the compiler helpfully warns us that completionHandler must be Sendable.
Unfortunately, DispatchGroup's notify gives us no such compiler warnings. Thus, we crash at runtime.
So my questions are:
Why can't the compiler warn us about a potential problem with DispatchGroup().notify(queue:) like it can with DispatchQueue.global().async?
How can we address this problem in a holistic way in our app, as it's a very simple mistake to make (with very bad consequences) while we migrate off GCD?
I'm sure the broader answer here is "don't mix GCD and Concurrency", but unfortunately that's a little unavoidable as we migrate our large legacy code base! 🙂
Hello everyone,
I’m a new developer still learning as I go. I’m building a simple watchOS app that tracks Apple Watch battery consumption, records hourly usage data, and uses that information to predict battery life in hours.
I’ve run into an issue where background refresh completely stalls after charging and never recovers, regardless of what I do. The only way to restore normal behavior is to restart the watch.
Background refresh can work fine for days, but if the watch is charging and a scheduled background refresh tries to run during that period, it appears to be deferred—and then remains in that deferred state indefinitely. Even reopening the app or scheduling new refreshes doesn’t recover it.
Has anyone else encountered this behavior? Is there a reliable workaround?
I’ve seen a few reports suggesting that there may be a regression in scheduleBackgroundRefresh() on watchOS 26, where tasks are never delivered after certain states.
Any insights or confirmations would be greatly appreciated. Thank you!
I am currently developing a macOS app that can show system HUDs in the Notch
Till Sequoia I used to kill the OSDUIHelper process (which displays the default macOS Volume and Brightness control HUDs) - and replaced it with my app's HUDs
But, it is not working on macOS Tahoe anymore as the OSDUIHelper process is no longer there due to the UI changes
Has the process been renamed - or is there any other way to kill the process?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Swift
macOS
SwiftUI
Background Tasks
First, our app communicates with our blood glucose monitor (CGM) using Bluetooth Low Energy (BLE).
On an iPhone 14 Pro with iOS 26.0.1, Bluetooth communication works properly even when the app is in the background and locked. Even if the phone and CGM are disconnected, the app continues to scan in the background and reconnects when the phone and CGM are back in close proximity. It won't be dormant in the background or when the screen is locked. This effectively ensures that diabetic users can monitor their blood glucose levels in real time.
However, after using iOS 26.0.1 on the iPhone 17, we've received user feedback about frequent disconnections in the background. Our logs indicate that Bluetooth communication is easily disconnected when switching to the background, and then easily dormant by the system, especially when the user's screen is locked. This situation significantly impacts users' blood glucose monitoring, and users are unacceptable. What can be done?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
IOBluetooth
Background Tasks
Core Bluetooth