Processes & Concurrency

RSS for tag

Discover how the operating system manages multiple applications and processes simultaneously, ensuring smooth multitasking performance.

Concurrency Documentation

Posts under Processes & Concurrency subtopic

Post

Replies

Boosts

Views

Activity

App is getting terminated by OS even after calling BG task expiration
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
1
0
109
Aug ’25
How can I get a Subscriber to subscribe to get only 4 elements from an array?
Hello, I am trying to implement a subscriber which specifies its own demand for how many elements it wants to receive from a publisher. My code is below: import Combine var array = [1, 2, 3, 4, 5, 6, 7] struct ArraySubscriber<T>: Subscriber { typealias Input = T typealias Failure = Never let combineIdentifier = CombineIdentifier() func receive(subscription: any Subscription) { subscription.request(.max(4)) } func receive(_ input: T) -> Subscribers.Demand { print("input,", input) return .max(4) } func receive(completion: Subscribers.Completion<Never>) { switch completion { case .finished: print("publisher finished normally") case .failure(let failure): print("publisher failed due to, ", failure) } } } let subscriber = ArraySubscriber<Int>() array.publisher.subscribe(subscriber) According to Apple's documentation, I specify the demand inside the receive(subscription: any Subscription) method, see link. But when I run this code I get the following output: input, 1 input, 2 input, 3 input, 4 input, 5 input, 6 input, 7 publisher finished normally Instead, I expect the subscriber to only "receive" elements 1, 2, 3, 4 from the array. How can I accomplish this?
0
0
127
Aug ’25
macOS 14 XPC vs Foundation XPC
I'm looking into a newer XPC API available starting with macOS 14. Although it's declared as a low-level API I can't figure it how to specify code signing requirement using XPCListener and XPCSession. How do I connect it with xpc_listener_set_peer_code_signing_requirement and xpc_connection_set_peer_code_signing_requirement which require xpc_listener_t and xpc_connection_t respectively? Foundation XPC is declared as a high-level API and provides easy ways to specify code signing requirements on both ends of xpc. I'm confused with all these XPC APIs and their future: Newer really high-level XPCListener and XPCSession API (in low-level framework???) Low-level xpc_listener_t & xpc_connection_t -like API. Is it being replaced by newer XPCListener and XPCSession? How is it related to High-level Foundation XPC? Are NSXPCListener and NSXPCConnection going to be deprecated and replaced by XPCListener and XPCSession??
2
0
748
Aug ’25
Using raise in GCD can cause timing issues with the signal mechanism.
when we use raise in GCD, the signal handler is executed asynchronously, whereas in pthread, it is executed synchronously as expected. example: #include &lt;Foundation/Foundation.h&gt; #include &lt;pthread/pthread.h&gt; static void HandleSignal(int sigNum, siginfo_t* signalInfo, void* userContext) { printf("handle signal %d\n", sigNum); printf("begin sleep\n"); sleep(3); printf("end sleep\n"); } void InstallSignal(void) { static const int g_fatalSignals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, SIGSYS, SIGTRAP, }; int fatalSignalsCount = sizeof(g_fatalSignals) / sizeof(int); struct sigaction action = {{0}}; action.sa_flags = SA_SIGINFO | SA_ONSTACK; #if defined(__LP64__) action.sa_flags |= SA_64REGSET; #endif sigemptyset(&amp;action.sa_mask); action.sa_sigaction = &amp;HandleSignal; struct sigaction pre_sa; for(int i = 0; i &lt; fatalSignalsCount; i++) { int sigResult = sigaction(g_fatalSignals[i], &amp;action, &amp;pre_sa); } } void* RaiseAbort(void *userdata) { raise(SIGABRT); printf("signal handler has finished\n"); return NULL; } int main(int argc, const char * argv[]) { InstallSignal(); dispatch_async(dispatch_get_global_queue(0, 0), ^{ raise(SIGABRT); // abort(); // abort() is ok RaiseAbort(nullptr); }); // pthread is ok // pthread_t tid; // int ret = pthread_create(&amp;tid, NULL, RaiseAbort, NULL); // if (ret != 0) { // fprintf(stderr, "create thread failed\n"); // return EXIT_FAILURE; // } [[NSRunLoop mainRunLoop] run]; return 0; } console log: signal handler has finished handle signal 6 begin sleep end sleep
4
0
222
Sep ’25
BGTaskScheduler fails to match unique identifiers to a registered wildcard handler for BGContinuedProcessingTask
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.
1
0
147
Sep ’25
Mac: Best way to distinguish native app process and script process spawned from executable (e.g. python node) through process_id
I'm working on a Mac app that receives a process ID via NSXPCConnection, and I'm trying to figure out the best way to determine whether that process is a native macOS app like Safari—with bundles and all—or just a script launched by something like Node or Python. The executable is signed with a Team ID using codesign. I was thinking about getting the executable's path as one way to handle it, but I’m wondering if there’s a more reliable method than relying on the folder structure.
1
0
209
Sep ’25
Electron app with Express + Python child processes not running in macOS production build
Hi all, I’ve built an Electron application that uses two child processes: An Express.js server A Python executable (packaged .exe/binary) During the development phase, everything works fine — the Electron app launches, both child processes start, and the app functions as expected. But when I create a production build for macOS, the child processes don’t run. Here’s a simplified snippet from my electron.mjs: import { app, BrowserWindow } from "electron"; import { spawn } from "child_process"; import path from "path"; let mainWindow; const createWindow = () =&gt; { mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: true, }, }); mainWindow.loadFile("index.html"); // Start Express server const serverPath = path.join(process.resourcesPath, "app.asar.unpacked", "server", "index.js"); const serverProcess = spawn(process.execPath, [serverPath], { stdio: "inherit", }); // Start Python process const pythonPath = path.join(process.resourcesPath, "app.asar.unpacked", "python", "myapp"); const pythonProcess = spawn(pythonPath, [], { stdio: "inherit", }); serverProcess.on("error", (err) =&gt; console.error("Server process error:", err)); pythonProcess.on("error", (err) =&gt; console.error("Python process error:", err)); }; app.whenReady().then(createWindow); I’ve already done the following: Configured package.json with the right build settings Set up extraResources / asarUnpack to include the server and Python files Verified both child processes work standalone Questions: What’s the correct way to package and spawn these child processes for macOS production builds? Do I need to move them into a specific location (like Contents/Resources/app.asar.unpacked) and reference them differently? Is there a more reliable pattern for handling Express + Python child processes inside an Electron app bundle? Any insights or working examples would be really appreciated!
2
0
85
Sep ’25
Helper app is sandboxed (entitlement + runtime check), but `URLsForDirectory:` returns user home (`/Users//`) instead of container path — why?
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/&lt;me&gt;/ (the real home). [[NSFileManager defaultManager] URLsForDirectory:inDomains:] and URLForDirectory:inDomain:appropriateForURL:create:error: return paths rooted at /Users/&lt;me&gt;/ (not under ~/Library/Containers/&lt;app_id&gt;/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/&lt;app_id&gt;/Data/tmp/TemporaryItems/NSIRD_&lt;helper_name&gt;_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/&lt;me&gt;/Library/Containers/&lt;app_id&gt;/Data/.... What I tried / diagnostics already gathered Entitlements &amp; 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/&lt;me&gt; URLsForDirectory: ( "file:///Users/&lt;me&gt;/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:&amp;err]; NSLog(@"NSItemReplacementDirectory: %@", tmpReplacement.path); Observed output (example): /Users/&lt;me&gt;/Library/Containers/&lt;app_id&gt;/Data/tmp/TemporaryItems/NSIRD_&lt;helper_name&gt;_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/&lt;me&gt;/... 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
6
0
170
Sep ’25
LLDB Cannot Load ODBC Driver Due to Sandbox Restrictions - How to Debug
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!
1
0
225
Sep ’25
Stay connected to Medical BLE device in background
I work for a large medical device company. We have a 1st party BLE enabled medical device that must be very battery efficient. To this end, if a connection is lost, the BLE radio is powered down after 60 seconds and will only turn back on when a physical button on the device is pressed. I've been tasked with connecting to the device, staying connected to the device, and being able to retrieve data from the device upon a timed action. For instance, this could include a data read and transmission while they sleep. The key part of this is staying reliably connected for extended periods of time. This is a BYOD setup, and we cannot control power profiles. I would very much appreciate any information, recommendations, and/or insights into solving this problem. Thanks!
1
0
352
Sep ’25
BGContinuedProcessingTask Notification Error
Hello im creating an expo module using this new API, but the problem i found currently testing this functionality is that when the task fails, the notification error doesn't go away and is always showing the failed task notification even if i start a new task and complete that one. I want to implement this module into the production app but i feel like having always the notification error might confuse our users or find it a bit bothersome. Is there a way for the users to remove this notification? Best regards!
1
0
94
Sep ’25
XPC codesign requirement crashes application
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/*
2
0
131
Sep ’25
Are iPad apps that are closed with the red traffic light prevented from running background tasks?
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?
1
0
123
Sep ’25
BGContinuedProcessingTask does not work on the official release of iOS 26
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
4
0
247
Sep ’25
Crash on DispatchQueue.main.sync from isolated thread
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?
4
0
141
Sep ’25
Control status item and login item from within app
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?
7
0
168
Sep ’25
Did GCD change in macOS 26
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… });
1
0
111
Sep ’25
Getting Started with SMAppService
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.
0
0
197
Sep ’25
Service Management Resources
Service Management framework supports installing and uninstalling services, including Service Management login items, launchd agents, and launchd daemons. General: Forums subtopic: App & System Services > Processes & Concurrency Forums tag: Service Management Service Management framework documentation Daemons and Services Programming Guide archived documentation Technote 2083 Daemons and Agents — It hasn’t been updated in… well… decades, but it’s still remarkably relevant. EvenBetterAuthorizationSample sample code — This has been obviated by SMAppService. SMJobBless sample code — This has been obviated by SMAppService. Sandboxing with NSXPCConnection sample code WWDC 2022 Session 10096 What’s new in privacy introduces the new SMAppService facility, starting at 07˸07 BSD Privilege Escalation on macOS forums post Getting Started with SMAppService forums post Background items showing up with the wrong name forums post Related forums tags include: XPC, Apple’s preferred inter-process communication (IPC) mechanism Inter-process communication, for other IPC mechanisms Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
2.2k
Sep ’25