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

Created

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
94
Jul ’25
Big Sur - LaunchAgents - Load error 5: input/output error
Hi all, I am having a mysterious problem trying to load a user LaunchAgent under Big Sur - It is the .plist of gniemetz's automount.sh  https://github.com/gniemetz/automount for mounting SMB shares via pwd access from the Keychain - Placed the .sh into /usr/local/bin, chmod 644 and chown user:staff Placed the LaunchAgent .plist into ~/Library/LaunchAgents (created LaunchAgents it as it didn't exist), same chmod/chown. drwxr-xr-x		3	 users		 96 Nov	1 22:13 LaunchAgents ~/Library/LaunchAgentsrw-r--r--		1	 users	 1038 Nov	1 22:13 it.niemetz.automount.plist /usr/local drwxr-xr-x		4 root		wheel		128 Nov	1 21:52 bin /usr/local/binrwxr-xr-x		1 root		wheel	30310 Oct 29 21:58 automount.sh then the following: Load failed: 5: Input/output error For the life of me, I cannot find anywhere what this means... launchctl start ~/Library/LaunchAgents/it.niemetz.automount.plist completes with no errors, syntax also parses OK /Users//Library/LaunchAgents/it.niemetz.automount.plist: OK I have added Terminal and /bin/bash to Full Disk Access under Security... Launching the script manually as /usr/local/bin/automount.sh works fine. Console shows system.log shows this when load -w is run: 00:27:14 mac-mini-Big-Sur com.apple.xpc.launchd[1] (com.apple.xpc.launchd.user.domain.1000002.100006.Aqua): entering bootstrap mode Nov	3 00:27:14 mac-mini-Big-Sur com.apple.xpc.launchd[1] (com.apple.xpc.launchd.user.domain.1000002.100006.Aqua): exiting bootstrap mode For easy reference the .plist is pasted at the end - Anyone seen this error before? Thanks! ++ Label it.niemetz.automount LimitLoadToSessionType Aqua RunAtLoad WatchPaths /etc/resolv.conf /Library/Preferences/SystemConfiguration/NetworkInterfaces.plist /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist ProgramArguments /usr/local/bin/automount.sh --mountall
20
2
43k
Nov ’20
Background Tasks Resources
General: Forums subtopic: App & System Services > Processes & Concurrency Forums tag: Background Tasks Background Tasks framework documentation UIApplication background tasks documentation ProcessInfo expiring activity documentation Using background tasks documentation for watchOS Performing long-running tasks on iOS and iPadOS documentation WWDC 2020 Session 10063 Background execution demystified — This is critical resource. Watch it! [1] WWDC 2022 Session 10142 Efficiency awaits: Background tasks in SwiftUI iOS Background Execution Limits forums post UIApplication Background Task Notes forums post Testing and Debugging Code Running in the Background forums post Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] Sadly the video is currently not available from Apple. I’ve left the link in place just in case it comes back.
0
0
3.8k
Jun ’22
BSD Privilege Escalation on macOS
This week I’m handling a DTS incident from a developer who wants to escalate privileges in their app. This is a tricky problem. Over the years I’ve explained aspects of this both here on DevForums and in numerous DTS incidents. Rather than do that again, I figured I’d collect my thoughts into one place and share them here. If you have questions or comments, please start a new thread with an appropriate tag (Service Management or XPC are the most likely candidates here) in the App & System Services > Core OS topic area. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" BSD Privilege Escalation on macOS macOS has multiple privilege models. Some of these were inherited from its ancestor platforms. For example, Mach messages has a capability-based privilege model. Others were introduced by Apple to address specific user scenarios. For example, macOS 10.14 and later have mandatory access control (MAC), as discussed in On File System Permissions. One of the most important privilege models is the one inherited from BSD. This is the classic users and groups model. Many subsystems within macOS, especially those with a BSD heritage, use this model. For example, a packet tracing tool must open a BPF device, /dev/bpf*, and that requires root privileges. Specifically, the process that calls open must have an effective user ID of 0, that is, the root user. That process is said to be running as root, and escalating BSD privileges is the act of getting code to run as root. IMPORTANT Escalating privileges does not bypass all privilege restrictions. For example, MAC applies to all processes, including those running as root. Indeed, running as root can make things harder because TCC will not display UI when a launchd daemon trips over a MAC restriction. Escalating privileges on macOS is not straightforward. There are many different ways to do this, each with its own pros and cons. The best approach depends on your specific circumstances. Note If you find operations where a root privilege restriction doesn’t make sense, feel free to file a bug requesting that it be lifted. This is not without precedent. For example, in macOS 10.2 (yes, back in 2002!) we made it possible to implement ICMP (ping) without root privileges. And in macOS 10.14 we removed the restriction on binding to low-number ports (r. 17427890). Nice! Decide on One-Shot vs Ongoing Privileges To start, decide whether you want one-shot or ongoing privileges. For one-shot privileges, the user authorises the operation, you perform it, and that’s that. For example, if you’re creating an un-installer for your product, one-shot privileges make sense because, once it’s done, your code is no longer present on the user’s system. In contrast, for ongoing privileges the user authorises the installation of a launchd daemon. This code always runs as root and thus can perform privileged operations at any time. Folks often ask for one-shot privileges but really need ongoing privileges. A classic example of this is a custom installer. In many cases installation isn’t a one-shot operation. Rather, the installer includes a software update mechanism that needs ongoing privileges. If that’s the case, there’s no point dealing with one-shot privileges at all. Just get ongoing privileges and treat your initial operation as a special case within that. Keep in mind that you can convert one-shot privileges to ongoing privileges by installing a launchd daemon. Just Because You Can, Doesn’t Mean You Should Ongoing privileges represent an obvious security risk. Your daemon can perform an operation, but how does it know whether it should perform that operation? There are two common ways to authorise operations: Authorise the user Authorise the client To authorise the user, use Authorization Services. For a specific example of this, look at the EvenBetterAuthorizationSample sample code. Note This sample hasn’t been updated in a while (sorry!) and it’s ironic that one of the things it demonstrates, opening a low-number port, no longer requires root privileges. However, the core concepts demonstrated by the sample are still valid. The packet trace example from above is a situation where authorising the user with Authorization Services makes perfect sense. By default you might want your privileged helper tool to allow any user to run a packet trace. However, your code might be running on a Mac in a managed environment, where the site admin wants to restrict this to just admin users, or just a specific group of users. A custom authorisation right gives the site admin the flexibility to configure authorisation exactly as they want. Authorising the client is a relatively new idea. It assumes that some process is using XPC to request that the daemon perform a privileged operation. In that case, the daemon can use XPC facilities to ensure that only certain processes can make such a request. Doing this securely is a challenge. For specific API advice, see this post. WARNING This authorisation is based on the code signature of the process’s main executable. If the process loads plug-ins [1], the daemon can’t tell the difference between a request coming from the main executable and a request coming from a plug-in. [1] I’m talking in-process plug-ins here. Plug-ins that run in their own process, such as those managed by ExtensionKit, aren’t a concern. Choose an Approach There are (at least) seven different ways to run with root privileges on macOS: A setuid-root executable The sudo command-line tool The authopen command-line tool AppleScript’s do shell script command, passing true to the administrator privileges parameter The osascript command-line tool to run an AppleScript The AuthorizationExecuteWithPrivileges routine, deprecated since macOS 10.7 The SMJobSubmit routine targeting the kSMDomainSystemLaunchd domain, deprecated since macOS 10.10 The SMJobBless routine, deprecated since macOS 13 An installer package (.pkg) The SMAppService class, a much-needed enhancement to the Service Management framework introduced in macOS 13 Note There’s one additional approach: The privileged file operation feature in NSWorkspace. I’ve not listed it here because it doesn’t let you run arbitrary code with root privileges. It does, however, have one critical benefit: It’s supported in sandboxed apps. See this post for a bunch of hints and tips. To choose between them: Do not use a setuid-root executable. Ever. It’s that simple! Doing that is creating a security vulnerability looking for an attacker to exploit it. If you’re working interactively on the command line, use sudo, authopen, and osascript as you see fit. IMPORTANT These are not appropriate to use as API. Specifically, while it may be possible to invoke sudo programmatically under some circumstances, by the time you’re done you’ll have code that’s way more complicated than the alternatives. If you’re building an ad hoc solution to distribute to a limited audience, and you need one-shot privileges, use either AuthorizationExecuteWithPrivileges or AppleScript. While AuthorizationExecuteWithPrivileges still works, it’s been deprecated for many years. Do not use it in a widely distributed product. The AppleScript approach works great from AppleScript, but you can also use it from a shell script, using osascript, and from native code, using NSAppleScript. See the code snippet later in this post. If you need one-shot privileges in a widely distributed product, consider using SMJobSubmit. While this is officially deprecated, it’s used by the very popular Sparkle update framework, and thus it’s unlikely to break without warning. If you only need escalated privileges to install your product, consider using an installer package. That’s by far the easiest solution to this problem. Keep in mind that an installer package can install a launchd daemon and thereby gain ongoing privileges. If you need ongoing privileges but don’t want to ship an installer package, use SMAppService. If you need to deploy to older systems, use SMJobBless. For instructions on using SMAppService, see Updating helper executables from earlier versions of macOS. For a comprehensive example of how to use SMJobBless, see the EvenBetterAuthorizationSample sample code. For the simplest possible example, see the SMJobBless sample code. That has a Python script to help you debug your setup. Unfortunately this hasn’t been updated in a while; see this thread for more. Hints and Tips I’m sure I’ll think of more of these as time goes by but, for the moment, let’s start with the big one… Do not run GUI code as root. In some cases you can make this work but it’s not supported. Moreover, it’s not safe. The GUI frameworks are huge, and thus have a huge attack surface. If you run GUI code as root, you are opening yourself up to security vulnerabilities. Appendix: Running an AppleScript from Native Code Below is an example of running a shell script with elevated privileges using NSAppleScript. WARNING This is not meant to be the final word in privilege escalation. Before using this, work through the steps above to see if it’s the right option for you. Hint It probably isn’t! let url: URL = … file URL for the script to execute … let script = NSAppleScript(source: """ on open (filePath) if class of filePath is not text then error "Expected a single file path argument." end if set shellScript to "exec " & quoted form of filePath do shell script shellScript with administrator privileges end open """)! // Create the Apple event. let event = NSAppleEventDescriptor( eventClass: AEEventClass(kCoreEventClass), eventID: AEEventID(kAEOpenDocuments), targetDescriptor: nil, returnID: AEReturnID(kAutoGenerateReturnID), transactionID: AETransactionID(kAnyTransactionID) ) // Set up the direct object parameter to be a single string holding the // path to our script. let parameters = NSAppleEventDescriptor(string: url.path) event.setDescriptor(parameters, forKeyword: AEKeyword(keyDirectObject)) // The `as NSAppleEventDescriptor?` is required due to a bug in the // nullability annotation on this method’s result (r. 38702068). var error: NSDictionary? = nil guard let result = script.executeAppleEvent(event, error: &error) as NSAppleEventDescriptor? else { let code = (error?[NSAppleScript.errorNumber] as? Int) ?? 1 let message = (error?[NSAppleScript.errorMessage] as? String) ?? "-" throw NSError(domain: "ShellScript", code: code, userInfo: nil) } let scriptResult = result.stringValue ?? "" Revision History 2025-03-24 Added info about authopen and osascript. 2024-11-15 Added info about SMJobSubmit. Made other minor editorial changes. 2024-07-29 Added a reference to the NSWorkspace privileged file operation feature. Made other minor editorial changes. 2022-06-22 First posted.
0
0
4.0k
Jun ’22
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 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.1k
Jun ’22
XPC Resources
https://developer.apple.com/forums/thread/708877 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
2.9k
Jun ’22
Called endBackgroundTask but not working
When my app enter to background, I start a background task, and when Expiration happens, I end my background task. The code likes below: backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (backgroundTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:backgroundTask]; backgroundTask = UIBackgroundTaskInvalid; [self cancel]; } }); }]; When the breakpoint is triggered at the endBackgroundTask line, I also get the following log: [BackgroundTask] Background task still not ended after expiration handlers were called: <UIBackgroundTaskInfo: 0x282d7ab40>: taskID = 36, taskName = Called by MyApp, from MyMethod, creationTime = 892832 (elapsed = 26). This app will likely be terminated by the system. Call UIApplication.endBackgroundTask(:) to avoid this. The log don't appear every time, so why is that? Is there something wrong with my code?
3
0
2.8k
May ’23
Concurrency Resources
Swift Concurrency Resources: DevForums tags: Concurrency The Swift Programming Language > Concurrency documentation Migrating to Swift 6 documentation WWDC 2022 Session 110351 Eliminate data races using Swift Concurrency — This ‘sailing on the sea of concurrency’ talk is a great introduction to the fundamentals. WWDC 2021 Session 10134 Explore structured concurrency in Swift — The table that starts rolling out at around 25:45 is really helpful. Swift Async Algorithms package Swift Concurrency Proposal Index DevForum post Why is flow control important? DevForums post Matt Massicotte’s blog Dispatch Resources: DevForums tags: Dispatch Dispatch documentation — Note that the Swift API and C API, while generally aligned, are different in many details. Make sure you select the right language at the top of the page. Dispatch man pages — While the standard Dispatch documentation is good, you can still find some great tidbits in the man pages. See Reading UNIX Manual Pages. Start by reading dispatch in section 3. WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD [1] WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage [1] Avoid Dispatch Global Concurrent Queues DevForums post Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] These videos may or may not be available from Apple. If not, the URL should help you locate other sources of this info.
0
0
1.8k
Nov ’23
Are XPCSession and XPCListener incomplete(ly documented)?
I've been experimenting with the new low-level Swift API for XPC (XPCSession and XPCListener). The ability to send and receive Codable messages is an appealing alternative to making an @objc protocol in order to use NSXPCConnection from Swift — I can easily create an enum type whose cases map onto the protocol's methods. But our current XPC code validates the incoming connection using techniques similar to those described in Quinn's "Apple Recommended" response to the "Validating Signature Of XPC Process" thread. I haven't been able to determine how to do this with XPCListener; neither the documentation nor the Swift interface have yielded any insight. The Creating XPC Services article suggests using Xcode's XPC Service template, which contains this code: let listener = try XPCListener(service: serviceName) { request in request.accept { message in performCalculation(with: message) } } The apparent intent is to inspect the incoming request and decide whether to accept it or reject it, but there aren't any properties on IncomingSessionRequest that would allow the service to make that decision. Ideally, there would be a way to evaluate a code signing requirement, or at least obtain the audit token of the requesting process. (I did notice that a function xpc_listener_set_peer_code_signing_requirement was added in macOS 14.4, but it takes an xpc_listener_t argument and I can't tell whether XPCListener is bridged to that type.) Am I missing something obvious, or is there a gap in the functionality of XPCListener and IncomingSessionRequest?
3
0
928
Apr ’24
Unsandboxed XPCService launches sandboxed child processes
I have an application, it has main process and some child processes. As we want those child processes to have their own minimum sandbox privilege, not inheriting from parent process, we plan to use XPCService which uses a NSTask to launch those child processes, so those child processes can have its own sandbox privilege. We plan to deliver the application to Mac App Store, so process mode is: the sandboxed main process builds connections to the unsandboxed XPCService, the unsandboxed XPCService launch those sandboxed child processes. Can this process mode pass the Mac App Store rules? I see, there is a rule that all processes must be sandboxed, including XPCService. But I tested locally, the Application downloaded from Mac apple store also launches unsandboxed XPCService, like OneDrive. Do you have any suggestions for my application scenario, sandboxed child processes having its own privilege not inheriting from parent?
4
0
1k
Apr ’24
Problem with NSSound playback in XPC service
Hello, I run into an issue on Monterey (12.7.5). I have a bundled XPC service in my application which is displaying some stuff and playin sounds via NSSound. I had a problem with playback due to service priority, so I use the trick with a reply block where I send a reply block to the service and basically just retain it and never call it. This worked fine so far, but we have users, predominantly on Monterey, who are having a problem with sound playback. It's choppy and distorted when their machine is under load (where "load" often just means playing a video on YouTube in Chrome). Is there anything else I can do to get the proper priority for my xpc service so I can avoid distorted sound? Additionally the service type is Application and RunLoopType is NSRunLoop with JoinExistingSession set to true. The QoS level of main queue is 0x21 (user interactive) and I'm calling all the NSSound APIs on main queue.
3
0
869
Sep ’24
Coordination of Video Capture and Audio Engine Start in iOS Development
Question: When implementing simultaneous video capture and audio processing in an iOS app, does the order of starting these components matter, or can they be initiated in any sequence? I have an actor responsible for initiating video capture using the setCaptureMode function. In this actor, I also call startAudioEngine to begin the audio engine and register a resultObserver. While the audio engine starts successfully, I notice that the resultObserver is not invoked when startAudioEngine is called synchronously. However, it works correctly when I wrap the call in a Task. Could you please explain why the synchronous call to startAudioEngine might be blocking the invocation of the resultObserver? What would be the best practice for ensuring both components work effectively together? Additionally, if I were to avoid using Task, what approach would be required? Lastly, is the startAudioEngine effective from the start time of the video capture (00:00)? Platform: Xcode 16, Swift 6, iOS 18 References: Classifying Sounds in an Audio Stream – In my case, the analyzeAudio() method is not invoked. Setting Up a Capture Session – Here, the focus is on video capture. Classifying Sounds in an Audio File Code Snippet: (For further details. setVideoCaptureMode() surfaces the problem.) // ensures all operations happen off of the `@MainActor`. actor CaptureService { ... nonisolated private let resultsObserver1 = ResultsObserver1() ... private func setUpSession() throws { .. } ... setVideoCaptureMode() throws { captureSession.beginConfiguration() defer { captureSession.commitConfiguration() } /* -- Works fine (analyseAudio is printed) Task { self.resultsObserver1.startAudioEngine() } */ self.resultsObserver1.startAudioEngine() // Does not work - analyzeAudio not printed captureSession.sessionPreset = .high try addOutput(movieCapture.output) if isHDRVideoEnabled { setHDRVideoEnabled(true) } updateCaptureCapabilities() }
5
0
891
Sep ’24
Timer driven refresh
I have an app that needs to refresh a server whenever a Contacts record is updated. I can observe Contacts, but that only seems to work when my app is running (and in foreground, which it cannot be on iPhone if the Contacts app is being updated). I want it to process, even if my app is in background, or has been terminated (swiped away), or after a phone restart. The only way I can think of is to periodically push a notification to the app from an external server. Is there any way to run a timer that sends a notification to the app on a periodic basis? The timers you can set seem to run even if the Clock app is swiped away, or following a phone restart. Is there anything like that I could use to wake my app periodically?
1
0
600
Oct ’24
Schedule A Daily Task in MacOS Sandbox App
I am new to building apps for MacOS using SwiftUI but built apps for iOS currently in the store. I built an events app that stores a bunch of dates. The issue I have is that after X amount of time, the app needs to generate more events. In iOS I would use Background task to handle this, runs once daily etc. After much research I am pointed to using a LaunchAgents with an Embedded Helper Tool https://developer.apple.com/documentation/Xcode/embedding-a-helper-tool-in-a-sandboxed-app I am following this post: https://developer.apple.com/forums/thread/721737?answerId=739716022#739716022 I am stuck on setting up the plist file and clicking the button to try to add the launch item in simulator I get the following error: did not register, error: Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted} If this is the incorrect approach please let me know as I am stuck. Thanks.
4
0
582
Oct ’24
Is it possible for CarPlay to establish a connection with a Bluetooth Low Energy (BLE) dongle when the phone app is fully closed or not running in the background?
I’m currently developing an app that communicates with a BLE dongle. When I swipe up to close the app on my phone, both the phone app and the CarPlay app are terminated. From the CarPlay interface, I can relaunch the app. My question is: Can CarPlay establish a connection with a BLE dongle when the phone app is fully closed or not running in the background?
3
1
653
Oct ’24
How to correctly deploy bundled launchdaemons/launchagents?
I'm working on an enterprise product that's mainly a daemon (with Endpoint Security) without any GUI component. I'm looking into the update process for daemons/agents that was introduced with Ventura (Link), but I have to say that the entire process is just deeply unfun. Really can't stress this enough how unfun. Anyway... The product bundle now contains a dedicated Swift executable that calls SMAppService.register for both the daemon and agent. It registers the app in the system preferences login items menu, but I also get an error. Error registering daemon: Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted} What could be the reason? I wouldn't need to activate the items, I just need them to be added to the list, so that I can control them via launchctl. Which leads me to my next question, how can I control bundled daemons/agents via launchctl? I tried to use launchctl enable and bootstrap, just like I do with daemons under /Library/LaunchDaemons, but all I get is sudo launchctl enable system/com.identifier.daemon sudo launchctl bootstrap /Path/to/daemon/launchdplist/inside/bundle/Library/LaunchDaemons/com.blub.plist Bootstrap failed: 5: Input/output error (not super helpful error message) I'm really frustrated by the complexity of this process and all of its pitfalls.
7
0
759
Oct ’24
NSMetadataQuery threading issues
The code below is a simplified form of part of my code for my Swift Package Manager, Swift 5.6.1, PromiseKit 6.22.1, macOS command-line executable. It accepts a Mac App Store app ID as the sole argument. If the argument corresponds to an app ID for an app that was installed from the Mac App Store onto your computer, the executable obtains some information from Spotlight via a NSMetadataQuery, then prints it to stdout. I was only able to get the threading to work by calling RunLoop.main.run(). The only way I was able to allow the executable to return instead of being stuck forever on RunLoop.main.run() was to call exit(0) in the closure passed to Promise.done(). The exit(0) causes problems for testing. How can I allow the executable to exit without explicitly calling exit(0), and how can I improve the threading overall? I cannot currently use Swift Concurrency (await/async/TaskGroup) because the executable must support macOS versions that don't support Swift Concurrency. A Swift Concurrency solution variant would be useful as additional info, though, because, sometime in the future, I might be able to drop support for macOS versions older than 10.15. Thanks for any help. import Foundation import PromiseKit guard CommandLine.arguments.count > 1 else { print("Missing adamID argument") exit(1) } guard let adamID = UInt64(CommandLine.arguments[1]) else { print("adamID argument must be a UInt64") exit(2) } _ = appInfo(forAdamID: adamID) .done { appInfo in if let jsonData = try? JSONSerialization.data(withJSONObject: appInfo), let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString.replacingOccurrences(of: "\\/", with: "/")) } exit(0) } RunLoop.main.run() func appInfo(forAdamID adamID: UInt64) -> Promise<[String: Any]> { Promise { seal in let query = NSMetadataQuery() query.predicate = NSPredicate(format: "kMDItemAppStoreAdamID == %d", adamID) query.searchScopes = ["/Applications"] var observer: NSObjectProtocol? observer = NotificationCenter.default.addObserver( forName: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query, queue: .main ) { _ in query.stop() defer { if let observer { NotificationCenter.default.removeObserver(observer) } } var appInfo: [String: Any] = [:] for result in query.results { if let result = result as? NSMetadataItem { var attributes = ["kMDItemPath"] attributes.append(contentsOf: result.attributes) for attribute in attributes { let value = result.value(forAttribute: attribute) switch value { case let date as Date: appInfo[attribute] = ISO8601DateFormatter().string(from: date) default: appInfo[attribute] = value } } } } seal.fulfill(appInfo) } DispatchQueue.main.async { query.start() } } }
7
0
908
Oct ’24
Python Backend alongside MacOS Swift application
Context/Project Idea: I'm currently developing a project that consists of a macOS application using Swift and a local Python backend that executes specific tasks such as processing data. The Python backend is the core of this project, while the Swift application is a mere interface to interact with it. These two project parts should be decoupled so the user can theoretically run their own backend and connect the Swift application to it. Likewise, the user should be able to connect to the shipped backend using, e.g. curl. Current plan: My main idea is to use launchctl to launch a launchd agent which runs the Python backend. The script launching the backend will generate an API key stored in a keychain access group. The Swift application can then get that key and access the backend. The user can always get that API key from the keychain if they want to connect to it programmatically. Here are the main questions I have currently: Python Interpreter Consistency: I'm exploring options such as cx_Freeze or PyInstaller to create a standalone Python executable for better system stability. Does anyone have experience with these tools in a macOS environment, or are there other reliable alternatives worth considering? Adding a Launchd Agent to Xcode: How can I add a launchd agent to my Xcode project to manage a Python executable built with cx_Freeze or PyInstaller? What steps should I follow to ensure it functions properly? Keychain Access for Launchd Agent: Is it feasible for a launchd agent to access a Keychain access group? What configurations or permissions are necessary to implement this? Thanks in advance!
3
0
935
Oct ’24
Changes in behaviour of [SMAppService registerAndReturnError:] after Sonoma 14.4
I am using [SMAppService registerAndReturnError:] to register a launch agent from a plist bundled in the app (before the registration call a matching unregister is done via unregisterWithCompletionHandler as suggested by the docs). The non standard thing is that I am doing that in a root gui login with sudo to bootstrap my launch agent into gui/0 domain. This worked well until Sonoma 14.4 - now the call fails with: Error Domain=SMAppServiceErrorDomain Code=125 "Domain does not support specified action" UserInfo={NSLocalizedFailureReason=Domain does not support specified action} which is not really helpful. For now, i've switche to just using launchctl bootout and launchctl bootstrap to get around this, but could anyone elaborate on what has changed? My feeling is that something has changed in the logic that determines the domain - could it be that even with sudo the target domain is gui/ not gui/0 ? As far as I can see there are no ways to specify the domain from the SMAppService APIs right? Also a weird thing is that if run the code in a raw terminal in root gui it works as previously (but out of security, no thing really runs as root, everything is a launch agent under some less privileged user, and before Sonoma 14.4 sudoing with that less privileged user did work for [SMAppService registerAndReturn], now it does not, and what is also strange, doing sudo - and then sudo su also shows the same error code 125.
2
0
651
Oct ’24
AsyncStream stops dispatching
Hello I'm a beginner to Swift Concurrency and have run into an issue with AsyncStream. I've run into a situation that causes an observing of a for loop to receiving a values from an AsyncStream. At the bottom is the code that you can copy it into a Swift Playground and run. The code is supposed to mock a system that has a service going through a filter to read and write to a connection. Here is a log of the prints 🙈🫴 setupRTFAsyncWrites Start ⬅️ Pretend to write 0 ➡️ Pretend to read 0 feed into filter 0 yield write data 1 🙈🫴 setupRTFAsyncWrites: write(1 bytes) ⬅️🙈🫴 Async Event: dataToDevice: 1 bytes ⬅️ Pretend to write 1 ➡️ Pretend to read 1 feed into filter 1 yield write data 2 // here our for loop should have picked up the value sent down the continuation. But instead it just sits here. Sample that can go into a playground //: A UIKit based Playground for presenting user interface import SwiftUI import PlaygroundSupport import Combine import CommonCrypto import Foundation class TestConnection { var didRead: ((Data) -&gt; ()) = { _ in } var count = 0 init() { } func write(data: Data) { // pretend we sent this to the BT device print("⬅️ Pretend to write \(count)") Task { try await Task.sleep(ms: 200) print("➡️ Pretend to read \(self.count)") self.count += 1 // pretend this is a response from the device self.didRead(Data([0x00])) } } } enum TestEvent: Sendable { /// ask some one to write this to the device case write(Data) /// the filter is done case handshakeDone } class TestFilter { var eventsStream: AsyncStream&lt;TestEvent&gt; var continuation: AsyncStream&lt;TestEvent&gt;.Continuation private var count = 0 init() { (self.eventsStream, self.continuation) = AsyncStream&lt;TestEvent&gt;.makeStream(bufferingPolicy: .unbounded) } func feed(data: Data) { print("\tfeed into filter \(count)") count += 1 if count &gt; 5 { print("\t✅ handshake done") self.continuation.yield(.handshakeDone) return } Task { // data delivered to us by a bluetooth device // pretend it takes time to process this and then we return with a request to write back to the connection try await Task.sleep(ms: 200) print("\tyield write data \(self.count)") // pretend this is a response from the device let result = self.continuation.yield(.write(Data([0x11]))) } } /// gives the first request to fire to the device for the handshaking sequence func start() -&gt; Data { return Data([0x00]) } } // Here we facilitate communication between the filter and the connection class TestService { private let filter: TestFilter var task: Task&lt;(), Never&gt;? let testConn: TestConnection init(filter: TestFilter) { self.filter = filter self.testConn = TestConnection() self.testConn.didRead = { [weak self] data in self?.filter.feed(data: data) } self.task = Task { [weak self] () in await self?.setupAsyncWrites() } } func setupAsyncWrites() async { print("🙈🫴 setupRTFAsyncWrites Start") for await event in self.filter.eventsStream { print("\t\t🙈🫴 setupRTFAsyncWrites: \(event)") guard case .write(let data) = event else { print("\t\t🙈🫴 NOT data to device: \(event)") continue } print("\t\t⬅️🙈🫴 Async Event: dataToDevice: \(data)") self.testConn.write(data: data) } // for // This shouldn't end assertionFailure("This should not end") } public func handshake() async { let data = self.filter.start() self.testConn.write(data: data) await self.waitForHandshakedone() } private func waitForHandshakedone() async { for await event in self.filter.eventsStream { if case .handshakeDone = event { break } continue } } } Task { let service = TestService(filter: TestFilter()) await service.handshake() print("Done") } /* This is what happens: 🙈🫴 setupRTFAsyncWrites Start ⬅️ Pretend to write 0 ➡️ Pretend to read 0 feed into filter 0 yield write data 1 🙈🫴 setupRTFAsyncWrites: write(1 bytes) ⬅️🙈🫴 Async Event: dataToDevice: 1 bytes ⬅️ Pretend to write 1 ➡️ Pretend to read 1 feed into filter 1 yield write data 2 // It just stops here, the `for` loop in setupAsyncWrites() should have picked up the event sent down the continuation after "yield write data 2" // It should say 🙈🫴 setupRTFAsyncWrites: write(1 bytes) ⬅️🙈🫴 Async Event: dataToDevice: 1 bytes */ extension Task&lt;Never, Never&gt; { public static func sleep(ms duration: UInt64) async throws { try await Task.sleep(nanoseconds: 1_000_000 * duration) } }
1
0
490
Oct ’24