I have a basic setup following WWDC 2020 on Safari Web Extensions and another one on XPC. The video even mentions that one can use UserDefaults or XPC to communicate with the host app. Here is my setup.
macOS 15.2, Xcode 16.2 A macOS app (all targets sandboxed, with an app group) with 3 targets:
- SwiftUI Hello World
- web extension
- XPC Service
The web extension itself works and can update UserDefaults, which can then be read by SwiftUI app - everything works by the book.
The app can communicate to the XPC service via NSXPCConnection - again, everything works fine.
The problem is that the web extension does not communicate with XPC, and this is what I need so that I can avoid using UserDefaults for larger and more complex payloads.
Web Ext handler code:
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { func beginRequest(with context: NSExtensionContext) { // Unpack the message from Safari Web Extension. let item = context.inputItems[0] as? NSExtensionItem let message = item?.userInfo?[SFExtensionMessageKey] // Update the value in UserDefaults. let defaults = UserDefaults(suiteName: "com.***.AppName.group") let messageDictionary = message as? [String: String] if messageDictionary?["message"] == "Word highlighted" { var currentValue = defaults?.integer(forKey: "WordHighlightedCount") ?? 0 currentValue += 1 defaults?.set(currentValue, forKey: "WordHighlightedCount") } let response = NSExtensionItem() response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ] os_log(.default, "setting up XPC connection") let xpcConnection = NSXPCConnection(serviceName: "com.***.AppName.AppName-XPC-Service") xpcConnection.remoteObjectInterface = NSXPCInterface(with: AppName_XPC_ServiceProtocol.self) xpcConnection.resume() let service = xpcConnection.remoteObjectProxyWithErrorHandler { error in os_log(.default, "Received error: %{public}@", error as CVarArg) } as? AppName_XPC_ServiceProtocol service?.performCalculation(firstNumber: 23, secondNumber: 19) { result in NSLog("Result of calculation XPC is: \(result)") os_log(.default, "Result of calculation XPC is: \(result)") context.completeRequest(returningItems: [response], completionHandler: nil) } } }
The error I'm getting:
Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.***.AppName.AppName-XPC-Service was invalidated: failed at lookup with error 3 - No such process."
What am I missing?
Thanks for the clarifications.
So, yeah, that pretty much confirms what I was talking about in my previous post. You can’t use an XPC service for this task because there’s no way that the same XPC service can be visible to both your app and your appex. I see this a lot with folks building other extension types, like Finder Sync extensions.
How you get around that depends on your specific circumstances. What do you want to happen when the appex connects to the service when the container app isn’t running?
There are two standard answers to that:
-
The IPC is optional, so you want it to fail.
-
The IPC is critical, so you want it to launch the app.
I suspect it’s the latter, but that’s not really feasible because launching an app is a heavyweight operation. I talk about this whole issue in more detail in XPC and App-to-App Communication DevForums
The solution is for you to install something smaller than your app that gets launched on demand. Ideally that’d be an XPC service but, as I’ve noted above, that doesn’t work. However, there’s another option, namely a launchd
agent.
These have a number of useful properties:
-
They’re relatively easy to install, using
SMAppService
. -
They support XPC, via the
MachServices
property in thelaunchd
property list. -
And they support launch on demand, so the agent only need to runs when your appex (or app) calls upon its services.
Oh, and one thing to watch out for here. Your appex is sandboxed, and the app sandbox prevents it from connecting to arbitrary XPC named endpoints. To make this work the named endpoint must be ‘within’ an app group claimed by the appex. For example, you might have an app group ID like group.com.example.my-app.group
and then set the XPC endpoint name to be group.com.example.my-app.group.xpc
.
Note that app groups themselves have some sharp edges on macOS. See App Groups: macOS vs iOS: Fight!
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"