I put together proof of concept daemon and agent based on advice from @eskimo. Looks like it's working.
It's a bit naive implementation. However feel free to point out shortcomings.
Protocols:
Code Block swift@objc(TestDaemonXPCProtocol) protocol TestDaemonXPCProtocol { func agentCheckIn(agentEndpoint: NSXPCListenerEndpoint, withReply reply: @escaping (Bool) -> Void)}@objc(TestAgentXPCProtocol) protocol TestAgentXPCProtocol { func doWork(task: String, withReply reply: @escaping (Bool) -> Void)}
Daemon:
Code Block swift@objc class AgentXPCConnector: NSObject, TestDaemonXPCProtocol{ let connectionEstablished = DispatchSemaphore(value: 0) var connection: NSXPCConnection? func agentCheckIn(agentEndpoint: NSXPCListenerEndpoint, withReply reply: @escaping (Bool) -> Void) { if connection == nil { logger.log("Agent checking in") connection = NSXPCConnection(listenerEndpoint: agentEndpoint) connection!.remoteObjectInterface = NSXPCInterface(with: TestAgentXPCProtocol.self) connection!.resume() reply(true) connectionEstablished.signal() } else { logger.error("There is an agent alredy connected") reply(false) } }}class DaemonXPCServer : NSObject, NSXPCListenerDelegate { let agentResponder = AgentXPCConnector() func waitForConnection() { let timeOut = DispatchTime.now() + DispatchTimeInterval.seconds(86400) switch agentResponder.connectionEstablished.wait(timeout: timeOut) { case .success: logger.log("Connection established") case .timedOut: logger.error("Timed out while waiting for connection") exit(1) } } func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { newConnection.exportedInterface = NSXPCInterface(with: TestDaemonXPCProtocol.self) newConnection.exportedObject = agentResponder newConnection.resume() return true }}let logger = Logger(subsystem: "cz.macadmin.xpcanon", category: "daemon")let server = DaemonXPCServer()let listener = NSXPCListener(machServiceName: "cz.macadmin.xpcanon.xpc")listener.delegate = server;listener.resume()logger.log("Daemon listening for agent connections")server.waitForConnection()logger.log("Creating agent remote object")let agent = server.agentResponder.connection!.synchronousRemoteObjectProxyWithErrorHandler { error in logger.error("Problem with the connection to the agent \(String(describing: error))")} as? TestAgentXPCProtocollogger.log("Making the agent to do some work!")agent!.doWork(task: "Work Work") { (reply) in if reply { logger.log("Work success!") } else { logger.log("Work fail!") }}logger.log("Daemon done")
Agent:
Code Block swift@objc class AgentXPC: NSObject, TestAgentXPCProtocol{ func doWork(task: String, withReply reply: @escaping (Bool) -> Void) { logger.log("Starting work") sleep(5) logger.log("Work DONE!") reply(true) }}class AgentAnonDelegate : NSObject, NSXPCListenerDelegate { func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { let exportedObject = AgentXPC() newConnection.exportedInterface = NSXPCInterface(with: TestAgentXPCProtocol.self) newConnection.exportedObject = exportedObject newConnection.resume() return true }}let logger = Logger(subsystem: "cz.macadmin.xpcanon", category: "agent")let anonDelegate = AgentAnonDelegate()let anonListener = NSXPCListener.anonymous()anonListener.delegate = anonDelegateanonListener.resume()let anonEndpoint = anonListener.endpointlet daemonConnection = NSXPCConnection(machServiceName: "cz.macadmin.xpcanon.xpc", options: NSXPCConnection.Options.privileged)daemonConnection.remoteObjectInterface = NSXPCInterface(with: TestDaemonXPCProtocol.self)daemonConnection.resume()let daemon = daemonConnection.synchronousRemoteObjectProxyWithErrorHandler { error in logger.log("Unable to connect to daemon")} as? TestDaemonXPCProtocolvar connectedToDaemon = falsewhile !connectedToDaemon { daemon!.agentCheckIn(agentEndpoint: anonEndpoint) { (reply) in logger.log("Passed endpoint to the deamon") connectedToDaemon = true } sleep(1)}logger.log("Agent is in the work loop")RunLoop.main.run()
LaunchDaemon.plist:
Code Block plist<?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>Label</key> <string>cz.macadmin.xpcdaemon</string> <key>Program</key> <string>/path/to/xpcdaemon</string> <key>RunAtLoad</key> <true/> <key>MachServices</key> <dict> <key>cz.macadmin.xpcanon.xpc</key> <true/> </dict></dict></plist>
Log output:
Code Block daemon Daemon listening for agent connectionsdaemon Agent checking indaemon Connection establisheddaemon Creating agent remote objectdaemon Making the agent to do some work!agent Passed endpoint to the deamonagent Starting workagent Agent is in the work loopagent Work DONE!daemon Work success!daemon Daemon done