I’m currently working on updating the Talking Moose to modern macOS, with an eye on a Mac App Store release. This differs from your stock XPC setup, as the Talking Moose consists of a GUI application for configuring it, and a background process for the animated character, the latter of which should be launched at login and stay running, instead of only being launched on-demand when someone makes explicit requests to the service.

Here are the things I had to do differently from Xcode’s standard XPC Service target template:

  • Create an Application Group in Xcode’s “Capabilities” tab of the target settings, and make sure it’s in the settings for the helper and the GUI that will launch it.
  • Rename the helper so its bundle identifier and name start with this group identifier
  • Use a Copy Files build phase that copies the helper into the application’s Contents/Library/LoginItems directory so SMLoginItemSetEnabled and launchd are both happy.
  • In the helper, do a slightly modified version of the XPC Service startup to create the XPCListener with -initWithMachServiceName: instead of with -serviceListener, using the helper’s bundle identifier as the Mach service name. The -resume call to the listener will not block in this case, so you can just run a regular run loop and call this in -applicationDidFinishLaunching: or so.
  • In the GUI application, similarly call -initWithMachServiceName:options: (again passing the helper’s bundle ID as the Mach service name) instead of -initWithServiceName: to create your connection.
  • Use -[NSUserDefaults initWithSuiteName:], passing the app group ID as the suite name to create an NSUserDefaults object that both the helper and the hosting GUI can see, so the helper can read settings set by the user using the GUI app.
  • Call SMLoginItemSetEnabled() to launch/quit your helper (and register it as a login item, before which you may also want to call LSRegisterURL((CFURLRef)helperURL, true); to make sure no helper in a second, older copy of your app will be launched instead by accident (if your helper is a .app like my login item – I’m informed this call won’t work with .xpc bundles).
  • Call ProcessSerialNumber myPSN = { 0, kCurrentProcess }; TransformProcessType( &myPSN, kProcessTransformToUIElementApplication ); early in the helper’s startup sequence to make SMLoginItemSetEnabled launch the helper as a LSUIElement and not as a GUI-less background process. (This step is probably not needed if your helper has no GUI of its own, but the Moose helper of course shows the Moose in a window, and half the time the window was just not there until I did this).

I think that is all I had to do. If this doesn’t work for you, you can read the sources, or contact me and see if I have an idea what may be missing (Particularly the project settings are a bit hairy to get right). And if you find the issue yourself, feel free to let me know so I can add helpful tips or missing information to this post.

Acknowledgements: I thank Michael Gorbach and Kent Sutherland for the information provided to figure out some of these things.