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

Processes & Concurrency Resources
General: DevForums subtopic: App & System Services > Processes & Concurrency Processes & concurrency covers a number of different technologies: Background Tasks Resources Concurrency Resources — This includes Swift concurrency. Service Management Resources XPC Resources Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
145
Jul ’25
Communicate with running app
I've written an app that uses On Idle (RunHandler) to prompt me to get up and stretch every 30 minutes. Is there anyway to query the running app to determine how long it's been since it last woke up and prompted me? Something like thru the apps properties by right clicking on the icon in the dock?
2
0
319
Dec ’24
How to safely maximize concurrent UI rendering
I'm using Swift 6 and tasks to concurrently process multiple PDF files for rendering, and it's working well. But currently I'm manually limiting the number of simultaneous tasks to 2 out of fear that the system might run many tasks concurrently without having enough RAM to do the PDF processing. Testing on a variety of devices, I've tried increasing the task limit and haven't seen any crashes, but I'm quite concerned about the possibility. Any given device might be using a lot of RAM at any moment, and any given PDF might strain resources more than the average PDF. Is there a recommended technique for handling this kind of scenario? Should I not worry about it and just go ahead and start a high number of tasks, trusting that the system won't run too many concurrently and therefore won't run out of RAM?
2
0
256
Mar ’25
Background service on MacOS
Hi, I'm working on an application on MacOS. It contains a port-forward feature on TCP protocol. This application has no UI, but a local HTTP server where user can access to configure this application. I found that my application always exit for unknown purpose after running in backgruond for minutes. I think this is about MacOS's background process controlling. Source codes and PKG installers are here: https://github.com/burningtnt/Terracotta/actions/runs/16494390417
3
0
251
Jul ’25
BGContinuedProcessingTask compatibility with background URLSession
My app does really large uploads. Like several GB. We use the AWS SDK to upload to S3. It seemed like using BGContinuedProcessingTask to complete a set of uploads for a particular item may improve UX as well as performance and reliability. When I tried to get BGContinuedProcessingTask working with the AWS SDK I found that the task would fail after maybe 30 seconds. It looked like this was because the app stopped receiving updates from the AWS upload and the task wants consistent updates. The AWS SDK always uses a background URLSession and this is not configurable. I understand the background URLSession runs in a separate process from the app and maybe that is why progress updates did not continue when the app was in the background. Is it expected that BGContinuedProcessingTask and background URLSession are not really compatible? It would not be shocking since they are 2 separate background APIs. Would the Apple recommendation be to use a normal URLSession for this, in which case AWS would need to change their SDK? Or does Apple think that BGContinuedProcessingTask should just not be used with uploads? In other words use an upload specific API. Thanks!
2
0
130
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
118
Aug ’25
Why is flow control important?
One challenging aspect of Swift concurrency is flow control, aka backpressure. I was explaining this to someone today and thought it better to post that explanation here, for the benefit of all. If you have questions or comments, start a new thread in App & System Services > Processes & Concurrency and tag with Swift and Concurrency. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Why is flow control important? In Swift concurrency you often want to model data flows using AsyncSequence. However, that’s not without its challenges. A key issue is flow control, aka backpressure. Imagine you have a network connection with a requests property that returns an AsyncSequence of Request values. The core of your networking code might be a loop like this: func processRequests(connection: Connection) async throws { for try await request in connection.requests { let response = responseForRequest(request) try await connection.reply(with: response) } } Flow control is important in both the inbound and outbound cases. Let’s start with the inbound case. If the remote peer is generating requests very quickly, the network is fast, and responseForRequest(_:) is slow, it’s easy to fall foul of unbounded memory growth. For example, if you use AsyncStream to implement the requests property, its default buffering policy is .unbounded. So the code receiving requests from the connection will continue to receive them, buffering them in the async stream, without any bound. In the worst case scenario that might run your process out of memory. In a more typical scenario it might result in a huge memory spike. The outbound case is similar. Imagine that the remote peer keeps sending requests but stops receiving them. If the reply(with:) method isn’t implemented correctly, this might also result in unbounded memory growth. The solution to this problem is flow control. This flow control operates independently on the send and receive side: On the send side, the code sending responses should notice that the network connection has asserted flow control and stop sending responses until that flow control lifts. In an async method, like the reply(with:) example shown above, it can simply not return until the network connection has space to accept the reply. On the receive side, the code receiving requests from the connection should monitor how many are buffered. If that gets too big, it should stop receiving. That causes the requests to pile up in the connection itself. If the network connection implements flow control properly [1], this will propagate to the remote peer, which should stop generating requests. [1] TCP and QUIC both implement flow control. Use them! If you’re tempted to implement your own protocol directly on top of UDP, consider how it should handle flow control. Flow control and Network framework Network framework has built-in support for flow control. On the send side, it uses a ‘push’ model. When you call send(content:contentContext:isComplete:completion:) the connection buffers the message. However, it only calls the completion handler when it’s passed that message to the network for transmission [2]. If you send a message and don’t receive this completion callback, it’s time to stop sending more messages. On the receive side, Network framework uses a ‘pull’ model. The receiver calls a receive method, like receiveMessage(completion:), which calls a completion handler when there’s a message available. If you’ve already buffered too many messages, just stop calling this receive method. These techniques are readily adaptable to Swift concurrency using Swift’s CheckedContinuation type. That works for both send and receive, but there’s a wrinkle. If you want to model receive as an AsyncSequence, you can’t use AsyncStream. That’s because AsyncStream doesn’t support flow control. So, you’ll need to come up with your own AsyncSequence implementation [3]. [2] Note that this doesn’t mean that the data has made it to the remote peer, or has even been sent on the wire. Rather, it says that Network framework has successfully passed the data to the transport protocol implementation, which is then responsible for getting it to the remote peer. [3] There’s been a lot of discussion on Swift Evolution about providing such an implementation but none of that has come to fruition yet. Specifically: The Swift Async Algorithms package provides AsyncChannel, but my understanding is that this is not yet ready for prime time. I believe that the SwiftNIO folks have their own infrastructure for this. They’re driving this effort to build such support into Swift Async Algorithms. Avoid the need for flow control In some cases you can change your design to avoid the need for control. Imagine that your UI needs to show the state of a remote button. The network connection sends you a message every time the button is depressed or released. However, your UI only cares about the current state. If you forward every messages from the network to your UI, you have to worried about flow control. To eliminate that worry: Have your networking code translate the message to reflect the current state. Use AsyncStream with a buffering policy of .bufferingNewest(1). That way there’s only ever one value in the stream and, if the UI code is slow for some reason, while it might miss some transitions, it always knows about the latest state. 2024-12-13 Added a link to the MultiProducerSingleConsumerChannel PR. 2024-12-10 First posted.
0
0
723
Dec ’24
Effect of App Nap on Timer
I'm developing a macOS application that tracks the duration of a user's session using a timer, which is displayed both in the main window and in an menu bar extra view. I have a couple of questions regarding the timer's behavior: What happens to the timer if the user closes the application's window (causing the app to become inactive) but does not fully quit it? Does the timer continue to run, pause, or behave in some other way? Will the app nap feature stop the timer when app is in-active state?
1
0
109
Mar ’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
63
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
187
Sep ’25
XPC Resources
XPC is the preferred inter-process communication (IPC) mechanism on Apple platforms. XPC has three APIs: The high-level NSXPCConnection API, for Objective-C and Swift The low-level Swift API, introduced with macOS 14 The low-level C API, which, while callable from all languages, works best with C-based languages General: Forums subtopic: App & System Services > Processes & Concurrency Forums tag: XPC Creating XPC services documentation NSXPCConnection class documentation Low-level API documentation XPC has extensive man pages — For the low-level API, start with the xpc man page; this is the original source for the XPC C API documentation and still contains titbits that you can’t find elsewhere. Also read the xpcservice.plist man page, which documents the property list format used by XPC services. Daemons and Services Programming Guide archived documentation WWDC 2012 Session 241 Cocoa Interprocess Communication with XPC — This is no longer available from the Apple Developer website )-: Technote 2083 Daemons and Agents — It hasn’t been updated in… well… decades, but it’s still remarkably relevant. TN3113 Testing and Debugging XPC Code With an Anonymous Listener XPC and App-to-App Communication forums post Validating Signature Of XPC Process forums post This forums post summarises the options for bidirectional communication This forums post explains the meaning of privileged flag Related tags include: Inter-process communication, for other IPC mechanisms Service Management, for installing and uninstalling Service Management login items, launchd agents, and launchd daemons Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
3.1k
3w
Guideline 3.2.1(viii) - Business - Other Business Model Issues - Acceptable
The support URL provided in App Store Connect must direct to a support page with links to a loan services privacy policy. The support page must also reference the lender or lending license. The privacy policy provided in App Store Connect must include references to the lender. The verified email domains associated with your Apple Developer Program account must match domains for the submitting company or partnered financial institution.
0
0
485
Dec ’24
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
83
Sep ’25
DispatchSourceTimer Not Firing in Local Push Connectivity Extension When App Is in Foreground and Device Is Locked
Hi, I’m using a Local Push Connectivity Extension and encountering an issue with DispatchSourceTimer. In my extension, I create a DispatchSourceTimer that is supposed to fire every 1 second. It works as expected at first. However, when the app is in the foreground and the device is locked, the timer eventually stops firing after 1–3 hours. The extension process is still alive, and no errors are thrown Has anyone experienced this behavior? Is this a known limitation for timers inside NEAppPushProvider, or is the extension being deprioritized silently by the system? Any insights or suggestions would be greatly appreciated. Thanks!
2
0
82
Apr ’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
92
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
132
Sep ’25
Help me implement SMAppServices
I have followed these steps as mentioned in this link :(https://developer.apple.com/forums/thread/721737) My projects app bundle structure is like this : TWGUI.app TWGUI.app/Contents TWGUI.app/Contents/_CodeSignature TWGUI.app/Contents/_CodeSignature/CodeResources TWGUI.app/Contents/MacOS TWGUI.app/Contents/MacOS/TWAgent TWGUI.app/Contents/MacOS/TWGUI TWGUI.app/Contents/Resources TWGUI.app/Contents/Library TWGUI.app/Contents/Library/LaunchAgents TWGUI.app/Contents/Library/LaunchAgents/com.example.TWGUI.agent.plist TWGUI.app/Contents/Info.plist TWGUI.app/Contents/PkgInfo TWGUI is my main GUI App , i which i want to embed TWAgent (a command line tool target) and register it using SMAppServices so that launchd can launch it. In TWGUI, code for registering to launchd using SMAppServices is structure as follow : import SwiftUI import ServiceManagement struct ContentView: View { let agent = SMAppService.agent(plistName: "com.example.TWGUI.agent.plist") var body: some View { VStack { Button("Register Agent") { RegisterAgent () } .padding() Button("Unregister Agent") { UnregisterAgent () } .padding() } } func RegisterAgent() { DispatchQueue.global(qos: .background).async { do { print("Registering Agent. Status: \(agent.status.rawValue)") try agent.register() print("Agent registered") } catch { print("Failed to register agent: \(error)") } } } func UnregisterAgent() { DispatchQueue.global(qos: .background).async { do { print("Unregistering Agent. Status: \(agent.status.rawValue)") try agent.unregister() print("Agent unregistered") } catch { print("Failed to unregister agent: \(error)") } } } } com.example.TWGUI.agent.plist : &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs$ &lt;plist version="1.0"&gt; &lt;dict&gt; &lt;key&gt;Label&lt;/key&gt; &lt;string&gt;com.example.TWGUI.agent&lt;/string&gt; &lt;key&gt;ProgramArguments&lt;/key&gt; &lt;array&gt; &lt;string&gt;Contents/MacOS/TWAgent&lt;/string&gt; &lt;/array&gt; &lt;key&gt;RunAtLoad&lt;/key&gt; &lt;true/&gt; &lt;key&gt;KeepAlive&lt;/key&gt; &lt;true/&gt; &lt;/dict&gt; &lt;/plist&gt; I have used ProgramArguements instead of using Program in above plist because i was getting this error when i was using Program earlier : Registering Agent. Status: 3 Failed to register agent: Error Domain=SMAppServiceErrorDomain Code=111 "Invalid or missing Program/ProgramArguments" UserInfo={NSLocalizedFailureReason=Invalid or missing Program/ProgramArguments} TWGUI apps Info.plist is : &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt; &lt;plist version="1.0"&gt; &lt;dict&gt; &lt;key&gt;BuildMachineOSBuild&lt;/key&gt; &lt;string&gt;23C71&lt;/string&gt; &lt;key&gt;CFBundleDevelopmentRegion&lt;/key&gt; &lt;string&gt;en&lt;/string&gt; &lt;key&gt;CFBundleExecutable&lt;/key&gt; &lt;string&gt;TWGUI&lt;/string&gt; &lt;key&gt;CFBundleIdentifier&lt;/key&gt; &lt;string&gt;com.example.TWAgent&lt;/string&gt; &lt;key&gt;CFBundleInfoDictionaryVersion&lt;/key&gt; &lt;string&gt;6.0&lt;/string&gt; &lt;key&gt;CFBundleName&lt;/key&gt; &lt;string&gt;TWGUI&lt;/string&gt; &lt;key&gt;CFBundlePackageType&lt;/key&gt; &lt;string&gt;APPL&lt;/string&gt; &lt;key&gt;CFBundleShortVersionString&lt;/key&gt; &lt;string&gt;1.0&lt;/string&gt; &lt;key&gt;CFBundleSupportedPlatforms&lt;/key&gt; &lt;array&gt; &lt;string&gt;MacOSX&lt;/string&gt; &lt;/array&gt; &lt;key&gt;CFBundleVersion&lt;/key&gt; &lt;string&gt;1&lt;/string&gt; &lt;key&gt;DTCompiler&lt;/key&gt; &lt;string&gt;com.apple.compilers.llvm.clang.1_0&lt;/string&gt; &lt;key&gt;DTPlatformBuild&lt;/key&gt; &lt;string&gt;&lt;/string&gt; &lt;key&gt;DTPlatformName&lt;/key&gt; &lt;string&gt;macosx&lt;/string&gt; &lt;key&gt;DTPlatformVersion&lt;/key&gt; &lt;string&gt;14.2&lt;/string&gt; &lt;key&gt;DTSDKBuild&lt;/key&gt; &lt;string&gt;23C53&lt;/string&gt; &lt;key&gt;DTSDKName&lt;/key&gt; &lt;string&gt;macosx14.2&lt;/string&gt; &lt;key&gt;DTXcode&lt;/key&gt; &lt;string&gt;1510&lt;/string&gt; &lt;key&gt;DTXcodeBuild&lt;/key&gt; &lt;string&gt;15C65&lt;/string&gt; &lt;key&gt;LSMinimumSystemVersion&lt;/key&gt; &lt;string&gt;14.2&lt;/string&gt; &lt;/dict&gt; &lt;/plist&gt; TWAgent target has main.swift file which does this : import Foundation let startTime = CFAbsoluteTimeGetCurrent() func logTimeSinceStart() { let elapsedTime = CFAbsoluteTimeGetCurrent() - startTime NSLog("Time since program started: \(elapsedTime) seconds") } func startLoggingTime() { Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in logTimeSinceStart() } } // Start logging time startLoggingTime() // Keep the run loop running CFRunLoopRun() I followed these exact same steps in another project earlier and my agent was getting registered, although i lost that project due to some reasons. But now i am getting this error when i am registering or unregistering agent using SMAppServices from the code above : Registering Agent. Status: 3 Failed to register agent: Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted} I tried diffrent fixes for like this : Moved app bundle to /applications folder Gave permission for full disc access to this app . Code sign again (both agent and TWGUI ... But nothing seems to work , getting same error. I tried to launch agent using : Launchctl load com.example.TWGUI.agent.plist and it worked , so there is no issue with my plist implementation. Can someone help me understand how can i solve this issue ? or if i am following right steps ? Can give steps need to follow to implement this and steps so that i can register and start my agent using SMAppServices? And i also tried the project give in apples official documentation : [https://developer.apple.com/documentation/servicemanagement/updating-your-app-package-installer-to-use-the-new-service-management-api) but got same error in this project as well .
2
0
122
Apr ’25