To establish a privileged helper daemon from a command line app to handle actions requiring root privileges I still use the old way of SMJobBless. But this is deprecated since OSX 10.13 and I want to finally update it to the new way using SMAppService.
As I'm concerned with securing it against malicious exploits, do you have a recommended up-to-date implementation in Objective-C establishing a privileged helper and verifying it is only used by my signed app?
I've seen the suggestion in the documentation to use SMAppService, but couldn't find a good implementation covering security aspects. My old implementation in brief is as follows:
bool runJobBless () {
// check if already installed
NSFileManager* filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath:@"/Library/PrivilegedHelperTools/com.company.Helper"] &&
[filemgr fileExistsAtPath:@"/Library/LaunchDaemons/com.company.Helper.plist"])
{
// check helper version to match the client
// ...
return true;
}
// create authorization reference
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate (NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status != errAuthorizationSuccess) return false;
// obtain rights to install privileged helper
AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
AuthorizationRights authRights = { 1, &authItem };
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
status = AuthorizationCopyRights (authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
if (status != errAuthorizationSuccess) return false;
// SMJobBless does it all: verify helper against app and vice-versa, place and load embedded launchd.plist in /Library/LaunchDaemons, place executable in /Library/PrivilegedHelperTools
CFErrorRef cfError;
if (!SMJobBless (kSMDomainSystemLaunchd, (CFStringRef)@"com.company.Helper", authRef, &cfError)) {
// check helper version to match the client
// ...
return true;
} else {
CFBridgingRelease (cfError);
return false;
}
}
void connectToHelper () {
// connect to helper via XPC
NSXPCConnection* c = [[NSXPCConnection alloc] initWithMachServiceName:@"com.company.Helper.mach" options:NSXPCConnectionPrivileged];
c.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol (SilentInstallHelperProtocol)];
[c resume];
// call function on helper and wait for completion
dispatch_semaphore_t semaphore = dispatch_semaphore_create (0);
[[c remoteObjectProxy] callFunction:^() {
dispatch_semaphore_signal (semaphore);
}];
dispatch_semaphore_wait (semaphore, dispatch_time (DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
dispatch_release (semaphore);
[c invalidate];
[c release];
}
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
I'm trying to understand how the API works to perform a function that can continue running if the user closes the app. For a very simple example, consider a function that increments a number on screen every second, counting from 1 to 100, reaching completion at 100. The user can stay in the app for 100s watching it work to completion, or the user can close the app say after 2s and do other things while watching it work to completion in the Live Activity.
To do this when the user taps a Start Counting button, you'd
1 Call BGTaskScheduler.shared.register(forTaskWithIdentifier:using:launchHandler:).
Question 1: Do I understand correctly, all of the logic to perform this counting operation would exist entirely in the launchHandler block (noting you could call another function you define passing it the task to be able to update its progress)? I am confused because the documentation states "The system runs the block of code for the launch handler when it launches the app in the background." but the app is already open in the foreground. This made me think this block is not going to be invoked until the user closes the app to inform you it's okay to continue processing in the background, but how would you know where to pick up. I want to confirm my thinking was wrong, that all the logic should be in this block from start to completion of the operation, and it's fine even if the app stays in the foreground the whole time.
2 Then you'd create a BGContinuedProcessingTaskRequest and set request.strategy = .fail for this example because you need it to start immediately per the user's explicit tap on the Start Counting button.
3 Call BGTaskScheduler.shared.submit(request).
Question 2: If the submit function throws an error, should you handle it by just performing the counting operation logic (call your function without passing a task)? I understand this can happen if for some reason the system couldn't immediately run it, like if there's already too many pending task requests. Seems you should not show an error message to the user, should still perform the request and just not support background continued processing for it (and perhaps consider showing a light warning "this operation can't be continued in the background so keep the app open"). Or should you still queue it up even though the user wants to start counting now? That leads to my next question
Question 3: In what scenario would you not want the operation to start immediately (the queue behavior which is the default), given the app is already in the foreground and the user requested some operation? I'm struggling to think of an example, like a button titled Compress Photos Whenever You Can, and it may start immediately or maybe it won't? While waiting for the launchHandler to be invoked, should the UI just show 0% progress or "Pending" until the system can get to this task in the queue? Struggling to understand the use cases here, why make the user wait to start processing when they might not even intend to close the app during the operation?
Thanks for any insights! As an aside, a sample project with a couple use cases would have been incredibly helpful to understand how the API is expected to be used.
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?
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?
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.
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()
}
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Sound Analysis
AVFoundation
Concurrency
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?
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.
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.
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()
}
}
}
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.
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) -> ()) = { _ 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<TestEvent>
var continuation: AsyncStream<TestEvent>.Continuation
private var count = 0
init() {
(self.eventsStream, self.continuation) = AsyncStream<TestEvent>.makeStream(bufferingPolicy: .unbounded)
}
func feed(data: Data) {
print("\tfeed into filter \(count)")
count += 1
if count > 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() -> Data {
return Data([0x00])
}
}
// Here we facilitate communication between the filter and the connection
class TestService {
private let filter: TestFilter
var task: Task<(), Never>?
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<Never, Never> {
public static func sleep(ms duration: UInt64) async throws {
try await Task.sleep(nanoseconds: 1_000_000 * duration)
}
}
Greetings, does anyone know if can I use background capabilities to refresh auth token safely without be aware of the OS kill this task?
Is there any Apple Oficial recommended flow to handle that?
thanks
I'm calling the following function in a SwiftUI View modifier in Xcode 16.1:
nonisolated function f -> CGFloat {
let semaphore = DispatchSemaphore(value: 0)
var a: CGFloat = 0
DispatchQueue.main.async {
a = ...
semaphore.signal()
}
semaphore.wait()
return a
}
The app freezes, and code in the main queue is never executed.
On iPhone, we can use iBeacon to wake up the APP in the background for Bluetooth scanning connection, now we want to port the function to AppleWatch APP, but the API related to iBeacon is not applicable on watchOS, does watchOS have a similar wake up mechanism?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
WatchKit
watchOS
Maps and Location
Core Bluetooth
Let's say I queue some tasks on DispatchQueue.global() and then switch to another app or locking screen for a while. The app was not terminated but stayed in the background.
Is there a chance that some tasks queued but not yet start could be discarded, even if the app hasn’t been terminated, after switching to another app or locking the screen for a while?
I created a macOS app, added an XPC service target, and also added a source editor extension.
in The source editor extension‘s perform function. It doesn't work
- (void)performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler {
self.xpcConnect = [[NSXPCConnection alloc] initWithServiceName:@"test.TestNewXPCApp.NewXPC"];
NSXPCInterface *interface = [NSXPCInterface interfaceWithProtocol:@protocol(NewXPCProtocol)];
self.xpcConnect.remoteObjectInterface = interface;
[self.xpcConnect resume];
[[self.xpcConnect remoteObjectProxy] performCalculationWithNumber:@231 andNumber:@119 withReply:^(NSNumber *reply) {
// We have received a response.
NSLog(@"ui success%@", reply);
}];
But In ViewControler.m, executing the same code , it can work.
So why is it possible to connect to the XPC service from within the macOS app, but not from the source editor extension?
When using conformance to ObservableObject and then doing async work in a Task, you will get a warning courtesy of Combine if you then update an @Published or @State var from anywhere but the main thread. However, if you are using @Observable there is no such warning.
Also, Thread.current is unavailable in asynchronous contexts, so says the warning. And I have read that in a sense you simply aren't concerned with what thread an async task is on.
So for me, that begs a question. Is the lack of a warning, which when using Combine is rather important as ignoring it could lead to crashes, a pretty major bug that Apple seemingly should have addressed long ago? Or is it just not an issue to update state from another thread, because Xcode is doing that work for us behind the scenes too, just as it manages what thread the async task is running on when we don't specify?
I see a lot of posts about this from around the initial release of Async/Await talking about using await MainActor.run {} at the point the state variable is updated, usually also complaining about the lack of a warning. But ow years later there is still no warning and I have to wonder if this is actually a non issue. On some ways similar to the fact that many of the early posts I have seen related to @Observable have examples of an @Observable ViewModel instantiated in the view as an @State variable, but in fact this is not needed as that is addressed behind the scenes for all properties of an @Observable type.
At least, that is my understanding now, but I am learning Swift coming from a PowerShell background so I question my understanding a lot.
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Concurrency
Swift
SwiftUI
Combine
Hello everyone!
I'm having a problem with background tasks running in the foreground.
When a user enters the app, a background task is triggered. I've written some code to check if the app is in the foreground and to prevent the task from running, but it doesn't always work. Sometimes the task runs in the background as expected, but other times it runs in the foreground, as I mentioned earlier.
Could it be that I'm doing something wrong? Any suggestions would be appreciated.
here is code:
class BackgroundTaskService {
@Environment(\.scenePhase) var scenePhase
static let shared = BackgroundTaskService()
private init() {}
// MARK: - create task
func createCheckTask() {
let identifier = TaskIdentifier.check
BGTaskScheduler.shared.getPendingTaskRequests { requests in
if requests.contains(where: { $0.identifier == identifier.rawValue }) {
return
}
self.createByInterval(identifier: identifier.rawValue, interval: identifier.interval)
}
}
private func createByInterval(identifier: String, interval: TimeInterval) {
let request = BGProcessingTaskRequest(identifier: identifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: interval)
scheduleTask(request: request)
}
// MARK: submit task
private func scheduleTask(request: BGProcessingTaskRequest) {
do {
try BGTaskScheduler.shared.submit(request)
} catch {
// some actions with error
}
}
// MARK: background actions
func checkTask(task: BGProcessingTask) {
let today = Calendar.current.startOfDay(for: Date())
let lastExecutionDate = UserDefaults.standard.object(forKey: "lastCheckExecutionDate") as? Date ?? Date.distantPast
let notRunnedToday = !Calendar.current.isDate(today, inSameDayAs: lastExecutionDate)
guard notRunnedToday else {
task.setTaskCompleted(success: true)
createCheckTask()
return
}
if scenePhase == .background {
TaskActionStore.shared.getAction(for: task.identifier)?()
}
task.setTaskCompleted(success: true)
UserDefaults.standard.set(today, forKey: "lastCheckExecutionDate")
createCheckTask()
}
}
And in AppDelegate:
BGTaskScheduler.shared.register(forTaskWithIdentifier: "check", using: nil) { task in
guard let task = task as? BGProcessingTask else { return }
BackgroundTaskService.shared.checkNodeTask(task: task)
}
BackgroundTaskService.shared.createCheckTask()
I have a process [command line cpp application] which i want to run always such as it should relaunch after a crash, after device startup etc.
I created a launchd Property List File with KeepAlive true and placed under /Library/LaunchDaemons.
Problem Statements:
I have a bash script to start and stop this process.
start using: launchctl bootstrap.
stop involve these two steps:
send SIGTERM signal and wait untill process stops after doing some cleanups
launchctl bootout [It doesn't sends SIGTERM]
during steps 1 - Process is getting stop, but also getting immediate relaunch by launchctl
during step 2 - it getting stop again.
is there a proper way so that we can disable KeepAlive temporarily so that process will not launch during step 1?
or suggest other ways to handle this?