Sandboxed XPC communication with a background process without launch-on-login

I have an app with the following simple architecture:

  1. Main App: A regular macOS app bundle with UI that allows users to customize settings of the app
  2. Helper: Another macOS app bundle with no UI (LSUIElement=1 in Info.plist) that is packaged inside the main app in the LoginItems directory doing the core tasks of the app in the background

My requirements are:

  1. Distribution via the MAS (=sandbox enabled for both targets)
  2. Both apps should be able to communicate via XPC
  3. The main app should be closable by the user at any time, should not keep running after being closed, whereas the helper app should as it performs actions for which it needs to be kept running in the background
  4. Launch-on-login of the helper app should not (and according to 2.4.5 (iii) of the ASRG must not) happen automatically w/o user consent and therefore I assume should always be a checkbox optional to the user

For sharing settings changed by the user in the main app with the helper too, I've added the Application Group capability to both targets to allow usage of a common user defaults suite.

While that works fine, there's the requirement that from within the main app I'd also need to request information and call a method from the background process (bidirectional communication) which is where I'm currently stuck.

I understand that an XPC Service (.xpc) would not be suitable for the helper here because it is automatically terminated when the parent app dies and may also not be suitable for my use cases as the helper needs to be able to request Screen Capture permissions from the user and I doubt this is possible for XPC bundles. I also understand that an XPC service which utilizes a mach-service XPC listener will only work in a sandboxed environment through the use of Service Management's SMLoginItemSetEnabled() API.

My main issue here is that the mandatory requirement to leave the option to launch the helper on login open to the user conflicts with the requirement of being able to communicate with the helper via XPC any time the main app is open, regardless of user choices.

If there wasn't the requirement to sandbox both apps, I would solve this issue with a launchd user agent that is kept alive but only runs at load if the user checked the launch-on-login box in the Settings of the main app. With sandbox enabled though, I'm currently launching the helper app manually if launch-on-login is disabled and let the Service Management API handle the lifecycle if it is enabled. For the first case, I haven't been able to establish an XPC connection w/o calling SMLoginItemSetEnabled() and I assume that is by design.

Is there something obvious I've missed here as I kinda feel like this is a typical app setup many other 3rd party devs are having as well?

Accepted Reply

Yeah, this an annoying corner case in the Mac App Store story:

  • You can’t establish XPC communication between arbitrary processes. For an XPC listener to work, the process must be managed by launchd.

    Well, on modern versions of macOS virtually all processes are managed by launchd in some way, but I’m using the term to indicate launchd daemons and agents, XPC Services, and Service Management login items.

  • None of these mechanisms work for your case. App Store apps aren’t allowed to install launchd daemons and agents. Service Management login items persist, which introduces a world of additional complexity. And standard XPC Services are bound to the lifetime of your app.

I did some playing around with this and I believe there might be a way forward. However, I need to discuss your specific case with the XPC team, and that’s not something I can do in the context of DevForums. Please open a DTS tech support incident and we can pick this up in that context.

Oh, and to be clear, there’s no guarantees here. The result of that discussion might be that the approach I’m thinking of is not valid.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

Yeah, this an annoying corner case in the Mac App Store story:

  • You can’t establish XPC communication between arbitrary processes. For an XPC listener to work, the process must be managed by launchd.

    Well, on modern versions of macOS virtually all processes are managed by launchd in some way, but I’m using the term to indicate launchd daemons and agents, XPC Services, and Service Management login items.

  • None of these mechanisms work for your case. App Store apps aren’t allowed to install launchd daemons and agents. Service Management login items persist, which introduces a world of additional complexity. And standard XPC Services are bound to the lifetime of your app.

I did some playing around with this and I believe there might be a way forward. However, I need to discuss your specific case with the XPC team, and that’s not something I can do in the context of DevForums. Please open a DTS tech support incident and we can pick this up in that context.

Oh, and to be clear, there’s no guarantees here. The result of that discussion might be that the approach I’m thinking of is not valid.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for your reply and for clarifying the current state of my case, I'll submit the DTS as proposed.

Add a Comment

@eskimo any update from your discussion?