I am developing a macOS non-interactive macOS application which does not show any ui.
i want to block main thread and do all the work on worker thread . Once done with work in worker thread, want to unblock main thread by exiting event loop to terminate application.
Because i dont want to show any UI or use any Foundation/Cocoa functionality, i am thinking of using CFRunLoop to block main thread from exiting until i finish my work in worker thread.
When i tried this in a project, I am able to finish work in worker thread after block main thread using CFRunLoop.
I also want this application to be a bundled application, which can be launched by double clicking on application bundle . But when i tried it in my xcode project by launching it using double clicking on application bundle, application keeps on toggling/bouncing in the dock menu with a status "Not responding". Although i am able to complete my work in worker thread.
import Foundation
let runLoop = CFRunLoopGetCurrent()
func workerTask() {
DispatchQueue.global().async {
print("do its work")
sleep(5) // do some work
print("calling exit event loop")
CFRunLoopStop(runLoop)
print ("unblocking main thread")
}
}
workerTask ()
// blocking main thread
print ("blocked main thread")
CFRunLoopRun()
print ("exit")
Why i am getting this application bouncing in doc menu behavior ? I tried by using NSApplicationMain instead of CFRunLoop in my project, in that case i didnt get this behavior .
Does NSApplicationMain does some extra work before starting NSRunLoop which i am not doing while using CFRunLoop, which is showing this toggling/Bouncing application icon in Dock menu ?
or Is this bouncing app icon issue is related to run loop i am using which is CFRunLoop ?
Note : If i dont use a bundled application and use a commandline application then i am able to do all steps in worker thread and exit main thread as i wanted after finishing my work . But i need to do all this in application which can be launched using double clicking (bundled applcation).
If not by using CFRunLoop, then how can i achive this ? - Create a application which shows no UI and do all work in worker thread while main thread is blocked. Once work is done unblock main thread and exit. And user should be able to launch application using double click the application icon.
Processes & Concurrency
RSS for tagDiscover how the operating system manages multiple applications and processes simultaneously, ensuring smooth multitasking performance.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hi, I have some questions regarding the Background Assets Extension and DeviceCheck framework.
Goal: Ensure that only users who have purchased the app can access the server's API without any user authentication using for example DeviceCheck framework and within a Background Assets Extension.
My app relies on external assets, which I'm loading using the Background Assets Extension. I'm trying to determine if it's possible to obtain a challenge from the server and send a DeviceCheck assertion during this process within the Background Assets Extension.
So far, I only receive session-wide authentication challenges—specifically NSURLAuthenticationMethodServerTrust in the Background Assets Extensio. I’ve tested with Basic Auth (NSURLAuthenticationMethodHTTPBasic) just for experimentation, but the delegate
func backgroundDownload(
_ download: BADownload,
didReceive challenge: URLAuthenticationChallenge
) async -> (URLSession.AuthChallengeDisposition, URLCredential?)
is never called with that authentication method. It seems task-specific challenges aren't coming through at all.
Also, while the DCAppAttestService API appears to be available on macOS, DCAppAttestService.isSupported always returns false (in my testing), which suggests it's not actually supported on macOS. Can anyone confirm if that’s expected behavior?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Network
DeviceCheck
Background Assets
Hi,
I am programming in C and would like to use Grand Central Dispatch for parallel computing (I mostly do physics based simulations). I remember there used to be example codes provided by Apple, but can't find those now. Instead I get the plain documentation. May anyone point me to the correct resources? It will be greatly appreciated. Thanks ☺.
I'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?
Desired Behavior
I want the app to be able to handle multiple Push-to-Start notifications even when it is completely terminated. Each Live Activity should:
Be successfully displayed upon receiving a Push-to-Start notification.
Trigger background tasks to send its update token to the server, regardless of the time interval between notifications.
Problem
I am facing an issue with iOS Live Activities when using Push-to-Start notifications to trigger Live Activities in an app that has been completely terminated. Here’s the detailed scenario:
When the app is completely terminated and I send the first Push-to-Start notification:
The Live Activity is successfully displayed.
didFinishLaunchingWithOptions` is triggered, and background tasks execute correctly, including sending the update token to the server.
When I send consecutive Push-to-Start notifications in quick succession (e.g., within a few seconds or minutes):
Both notifications successfully display their respective Live Activities.
Background tasks are executed correctly for both notifications.
However, when there is a longer interval (e.g., 10 minutes) between two Push-to-Start notifications:
The first notification works perfectly—it displays the Live Activity, triggers didFinishLaunchingWithOptions, and executes background tasks.
The second notification successfully displays the Live Activity but fails to execute any background tasks, such as sending the update token to the server.
My HypothesisI
suspect that iOS might impose a restriction where background runtime for Push-to-Start notifications can only be granted once within a certain time frame after the app has been terminated.
Any insights into why this issue might be occurring or how to ensure consistent background task execution for multiple Push-to-Start notifications would be greatly appreciated!
Hello Apple Developer Community,
I am developing a medical app that is classified as Class B according to FDA regulations. The app connects to a medical device using Bluetooth Low Energy (BLE) to collect critical medical data such as ECG readings. To ensure accurate data collection and maintain the quality of the medical readings, the app needs to wake up every five minutes in the background and perform tasks for approximately 30 seconds.
I understand that iOS has strict limitations on background execution to preserve battery and system performance. However, due to the medical nature of the app and the need for periodic data collection, I am seeking guidance on the following:
If I can provide documentation that the app is associated with an FDA-approved Class B medical device, would Apple allow more lenient background task execution policies?
Are there specific APIs, such as BackgroundTasks, CoreBluetooth, or other recommended strategies, that could help me achieve this behavior reliably?
Is there a process to apply for an exception or special consideration for medical apps that require periodic background activity?
Any insights or recommendations would be greatly appreciated. Thank you!
On MacOS 26 Tahoe, we are getting a background warning message stating, “App is running in the background…”
Is this expected behavior on the new OS?
Thanks
Asutos
Topic:
App & System Services
SubTopic:
Processes & Concurrency
I would like to implement an expression that pops out from the window to Immersive based on the following WWDC video.
This video introduces the new features of visionOS 2.0 in the form of refurbishing Apple's sample app BOT-anist.
https://developer.apple.com/jp/videos/play/wwdc2024/10153/?time=1252
In the video, it looks like ImmersiveSpaceAppModel is newly implemented.
However, the key code is not mentioned anywhere.
You pass appModel.robot as the from argument to the transform method of RealityViewContent.
It seems that BOT-anist has been updated once and can be downloaded from the following URL, but there is no class such as ImmersiveSpaceAppModel implemented in this app either.
https://developer.apple.com/documentation/visionos/bot-anist
Has it been further updated again?
Frankly, I'm not sure if it is possible to proceed as per the WWDC video.
Translated with DeepL.com (free version)
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Swift
RealityKit
Reality Composer Pro
visionOS
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 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs$
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.TWGUI.agent</string>
<key>ProgramArguments</key>
<array>
<string>Contents/MacOS/TWAgent</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
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 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>23C71</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>TWGUI</string>
<key>CFBundleIdentifier</key>
<string>com.example.TWAgent</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>TWGUI</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string></string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>14.2</string>
<key>DTSDKBuild</key>
<string>23C53</string>
<key>DTSDKName</key>
<string>macosx14.2</string>
<key>DTXcode</key>
<string>1510</string>
<key>DTXcodeBuild</key>
<string>15C65</string>
<key>LSMinimumSystemVersion</key>
<string>14.2</string>
</dict>
</plist>
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 .
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!
Recently, after updating the Developer app to the latest version, my iPad has been unable to open this app as it crashes immediately upon launch. Prior to the update, the app functioned normally. My device is an 11-inch iPad Pro from 2021, running iPadOS 17.3. I have tried troubleshooting steps such as reinstalling the app and restarting the device, but these actions have not resolved the issue. However, I need to use this specific version of the system, iPadOS 17.3, for software testing purposes and cannot upgrade the system. Other apps on my device work normally without any issues. Is there a solution to this problem? I have attempted to contact the developer support team in China, but they were also unable to provide a resolution. This issue is reproducible 100% of the time on my iPad.
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.
I'm working on implementing file moving with NSFileCoordinator. I'm using the slightly newer asynchronous API with the NSFileAccessIntents. My question is, how do I go about notifying the coordinator about the item move? Should I simply create a new instance in the asynchronous block? Or does it need to be the same coordinator instance?
let writeQueue = OperationQueue()
public func saveAndMove(data: String, to newURL: URL) {
let oldURL = presentedItemURL!
let sourceIntent = NSFileAccessIntent.writingIntent(with: oldURL, options: .forMoving)
let destinationIntent = NSFileAccessIntent.writingIntent(with: newURL, options: .forReplacing)
let coordinator = NSFileCoordinator()
coordinator.coordinate(with: [sourceIntent, destinationIntent], queue: writeQueue) { error in
if let error {
return
}
do {
// ERROR: Can't access NSFileCoordinator because it is not Sendable (Swift 6)
coordinator.item(at: oldURL, willMoveTo: newURL)
try FileManager.default.moveItem(at: oldURL, to: newURL)
coordinator.item(at: oldURL, didMoveTo: newURL)
} catch {
print("Failed to move to \(newURL)")
}
}
}
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Files and Storage
Swift
iCloud Drive
Concurrency
Hello,
An application I am working on would like to schedule push notifications for a medication reminder app. I am trying to use BGTaskScheduler to wake up periodically and submit the notifications based on the user's medication schedule.
I set up the task registration in my AppDelegate's didFinishLaunchingWithOptions method:
BGTaskScheduler.shared.register(
forTaskWithIdentifier: backgroundTaskIdentifier,
using: nil) { task in
self.scheduleNotifications()
task.setTaskCompleted(success: true)
self.scheduleAppRefresh()
}
scheduleAppRefresh()
I then schedule the task using:
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: backgroundTaskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 1)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
}
}
In my testing, I can see the background task getting called once, but if I do not launch the application during the day. The background task does not get called the next day.
Is there something else I need to add to get repeated calls from the BGTaskScheduler?
Thank You,
JR
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Background Tasks
User Notifications
Hi! I've been developing iOS and macOS apps for many years, but now I am looking to dive into smth i have never touched before, namely privileged helpers, and i am struggling hard trying to find my footing.
Here’s my use case: I have a CLI tool that requires elevated privileges. I want to create a menu bar app that can interact with this tool, but I’m struggling to find solid documentation or examples of how to accomplish this using SMAppService. I might just be missing something obvious.
If anyone could point me toward relevant documentation, examples, articles, tutorials, or even a WWDC session that covers running privileged helpers with SMAppService, I would greatly appreciate it.
Thanks in advance!
Hello!
I'm writing a System Extension that is an Endpoint Security client. And I want to Deny/Allow executing some XPC Service processes (using the ES_EVENT_TYPE_AUTH_EXEC event) depending on characteristics of a process that starts the XPC Service.
For this purpose, I need an API that could allow me to obtain an execution context of the XPC Service process. I can obtain this information using the "sudo launchctl procinfo <pid>" command (e.g. I can use the "domain = pid/3428" part of the output for this purpose). Also, I know that when the xpcproxy process is started, it gets as the arguments a service name and a pid of the process that requests the service so I can grasp the execution context from xpcproxy launching. But are these ways to obtain this info legitimate?
I implemented BGContinuedProcessingTask in my app and it seems to be working well for everyone except one user (so far) who has reached out to report nothing happens when they tap the Start Processing button. They have an iPhone 12 Pro Max running iOS 26.1. Restarting iPhone does not fix it.
When they turn off the background processing feature in the app, it works. In that case my code directly calls the function to start processing instead of waiting for it to be invoked in the register block (or submit catch block).
Is this a bug that's possible to occur, maybe device specific? Or have I done something wrong in the implementation?
func startProcessingTapped(_ sender: UIButton) {
if isBackgroundProcessingEnabled {
startBackgroundContinuedProcessing()
} else {
startProcessing(backgroundTask: nil)
}
}
func startBackgroundContinuedProcessing() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: .main) { @Sendable [weak self] task in
guard self != nil else { return }
startProcessing(backgroundTask: task as? BGContinuedProcessingTask)
}
let request = BGContinuedProcessingTaskRequest(identifier: taskIdentifier, title: title, subtitle: subtitle)
request.strategy = .fail
if BGTaskScheduler.supportedResources.contains(.gpu) {
request.requiredResources = .gpu
}
do {
try BGTaskScheduler.shared.submit(request)
} catch {
startProcessing(backgroundTask: nil)
}
}
func startProcessing(backgroundTask: BGContinuedProcessingTask?) {
// FIXME: Never called for this user when isBackgroundProcessingEnabled is true
}
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
If I create a BGContinuedProcessingTaskRequest, register it, and then "do work" within it appropriately reporting progress, and before my task has finished doing all the work it had to do, its expirationHandler triggers...
does the task later try again?
Or does it lose the execution opportunity until the app is next re-launched to the foreground?
In my testing, I never saw my task execute again once expired (which suggests the latter?).
I was able to easily force this expiry by starting my task, backgrounding my app, then launching the iOS Camera App. My example is just using test code inspired from https://developer.apple.com/documentation/backgroundtasks/performing-long-running-tasks-on-ios-and-ipados
let request = BGContinuedProcessingTaskRequest(identifier: taskIdentifier, title: "Video Upload", subtitle: "Starting Upload")
request.strategy = .queue
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in
guard let task = task as? BGContinuedProcessingTask else { return }
print("i am a good task")
var wasExpired = false
task.expirationHandler = {
wasExpired = true
}
let progress = task.progress
progress.totalUnitCount = 100
while !progress.isFinished && !wasExpired {
progress.completedUnitCount += 1
let formattedProgress = String(format: "%.2f", progress.fractionCompleted * 100)
task.updateTitle(task.title, subtitle: "Completed \(formattedProgress)%")
sleep(1)
}
if progress.isFinished {
print ("i was a good task")
task.setTaskCompleted(success: true)
} else {
print("i was not a good task")
task.setTaskCompleted(success: false)
}
}
try? BGTaskScheduler.shared.submit(request)
Apologies if this is clearly stated somewhere and I'm missing it.
This comes up over and over, here on the forums and elsewhere, so I thought I’d post my take on it. If you have questions or comments, start a new thread here on the forums. Put it in the App & System Services > Processes & Concurrency subtopic and tag it with Concurrency.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Waiting for an Async Result in a Synchronous Function
On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function.
Lemme say that again, with emphasis…
On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function.
This post dives into the details of this reality.
Prime Offender
Imagine you have an asynchronous function and you want to call it from a synchronous function:
func someAsynchronous(input: Int, completionHandler: @escaping @Sendable (_ output: Int) -> Void) {
… processes `input` asynchronously …
… when its done, calls the completion handler with the result …
}
func mySynchronous(input: Int) -> Int {
… calls `someAsynchronous(…)` …
… waits for it to finish …
… results the result …
}
There’s no good way to achieve this goal on Apple platforms. Every approach you might try has fundamental problems.
A common approach is to do this working using a Dispatch semaphore:
func mySynchronous(input: Int) -> Int {
fatalError("DO NOT WRITE CODE LIKE THIS")
let sem = DispatchSemaphore(value: 0)
var result: Int? = nil
someAsynchronous(input: input) { output in
result = output
sem.signal()
}
sem.wait()
return result!
}
Note This code produces a warning in the Swift 5 language mode which turns into an error in the Swift 6 language mode. You can suppress that warning with, say, a Mutex. I didn’t do that here because I’m focused on a more fundamental issue here.
This code works, up to a point. But it has unavoidable problems, ones that don’t show up in a basic test but can show up in the real world. The two biggest ones are:
Priority inversion
Thread pools
I’ll cover each in turn.
Priority Inversion
Apple platforms have a mechanism that helps to prevent priority inversion by boosting the priority of a thread if it holds a resource that’s needed by a higher-priority thread. The code above defeats that mechanism because there’s no way for the system to know that the threads running the work started by someAsynchronous(…) are being waited on by the thread blocked in mySynchronous(…). So if that blocked thread has a high-priority, the system can’t boost the priority of the threads doing the work.
This problem usually manifests in your app failing to meet real-time goals. An obvious example of this is scrolling. If you call mySynchronous(…) from the main thread, it might end up waiting longer than it should, resulting in noticeable hitches in the scrolling.
Threads Pools
A synchronous function, like mySynchronous(…) in the example above, can be called by any thread. If the thread is part of a thread pool, it consumes a valuable resource — that is, a thread from the pool — for a long period of time. The raises the possibility of thread exhaustion, that is, where the pool runs out of threads.
There are two common thread pools on Apple platforms:
Dispatch
Swift concurrency
These respond to this issue in different ways, both of which can cause you problems.
Dispatch can choose to over-commit, that is, start a new worker thread to get work done while you’re hogging its existing worker threads. This causes two problems:
It can lead to thread explosion, where Dispatch starts dozens and dozens of threads, which all end up blocked. This is a huge waste of resources, notably memory.
Dispatch has an hard limit to how many worker threads it will create. If you cause it to over-commit too much, you’ll eventually hit that limit, putting you in the thread exhaustion state.
In contrast, Swift concurrency’s thread pool doesn’t over-commit. It typically has one thread per CPU core. If you block one of those threads in code like mySynchronous(…), you limit its ability to get work done. If you do it too much, you end up in the thread exhaustion state.
WARNING Thread exhaustion may seem like just a performance problem, but that’s not the case. It’s possible for thread exhaustion to lead to a deadlock, which blocks all thread pool work in your process forever.
There’s a trade-off here. Swift concurrency doesn’t over-commit, so it can’t suffer from thread explosion but is more likely deadlock, and vice versa for Dispatch.
Bargaining
Code like the mySynchronous(…) function shown above is fundamentally problematic. I hope that the above has got you past the denial stage of this analysis. Now let’s discuss your bargaining options (-:
Most folks don’t set out to write code like mySynchronous(…). Rather, they’re working on an existing codebase and they get to a point where they have to synchronously wait for an asynchronous result. At that point they have the choice of writing code like this or doing a major refactor.
For example, imagine you’re calling mySynchronous(…) from the main thread in order to update a view. You could go down the problematic path, or you could refactor your code so that:
The current value is always available to the main thread.
The asynchronous code updates that value in an observable way.
The main thread code responds to that notification by updating the view from the current value.
This refactoring may or may not be feasible given your product’s current architecture and timeline. And if that’s the case, you might end up deploying code like mySynchronous(…). All engineering is about trade-offs. However, don’t fool yourself into thinking that this code is correct. Rather, make a note to revisit this choice in the future.
Async to Async
Finally, I want to clarify that the above is about synchronous functions. If you have a Swift async function, there is a good path forward. For example:
func mySwiftAsync(input: Int) async -> Int {
let result = await withCheckedContinuation { continuation in
someAsynchronous(input: input) { output in
continuation.resume(returning: output)
}
}
return result
}
This looks like it’s blocking the current thread waiting for the result, but that’s not what happens under the covers. Rather, the Swift concurrency worker thread that calls mySwiftAsync(…) will return to the thread pool at the await. Later, when someAsynchronous(…) calls the completion handler and you resume the continuation, Swift will grab a worker thread from the pool to continue running mySwiftAsync(…).
This is absolutely normal and doesn’t cause the sorts of problems you see with mySynchronous(…).
IMPORTANT To keep things simple I didn’t implement cancellation in mySwiftAsync(…). In a real product it’s important to support cancellation in code like this. See the withTaskCancellationHandler(operation:onCancel:isolation:) function for the details.