I have an idea for a useful, if niche, function that I'd like to write in Swift. It's inspired by the rendezvous(2)
system call from Plan 9. Here's the idealized signature in Swift (sans generics):
func rendezvous(machPortName: String, value: Any?) async -> Any?
Here, rendezvous is a primitive that encapsulates all the logic for creating a direct XPC connection between two macOS processes. The process is coordinated by a LaunchAgent or LaunchDaemon that both processes have access to. If P1 calls rendezvous first, execution is suspended (in this case, control is returned to the main loop) until P2 calls rendezvous. At that point, in P1, rendezvous returns an XPC remote object proxy to P2's value
and vice versa. Either process can choose not to vend a service to the other process by setting value
to nil
.
The details of how this works aren't that important, but the gist is that each process makes an XPC connection to the LaunchAgent, P1 creates an anonymous XPC listener, and sends a listener endpoint to the LaunchAgent which forwards it to P2. At that point, P2 can make a direct connection to P1 using the listener endpoint.
The question is, how close can I get to this idealized function signature with Swift's generics? The implementation needs to fill in the following properties on NSXPCConnection
:
-
exportedObject
- this isvalue
-
exportedInterface
- a wrapper around anObjectiveC.Protocol
thatvalue
conforms to. In other words, an@objc protocol
. This means that even thoughexportedObject
is anAny
, it actually has to be a reference type. -
remoteObjectInterface
- another@objc protocol
wrapper, representing the service that the remote process is vending to us. This is related to rendezvous's return type.
The closest we could get to the idealized signature would be to be able to restrict value
to be an existential, but I'm not sure if that's possible:
func rendezvous<T>(machPortName: String, value: T?) where T is an @objc protocol {
...
let connection: NSXPCConnection = ...
...
if let value = value {
connection.exportedObject = value
connection.exportedInterface = NSXPCInterface(with: T.self)
}
...
}
Similarly, for the return value, the closest to ideal would be if we could constrain the return value to be an existential conforming to an @objc protocol
:
func rendezvous<U>(machPortName: String, value: Any?) -> U? where U is an @objc protocol {
...
let connection: NSXPCConnection = ...
if connectionVendsARemoteObject {
connection.remoteInterface = NSXPCInterface(with: U.self)
return connection.remoteObjectProxy as! U
} else {
return nil
}
}
The call site would look something like this:
@objc protocol RemoteService {}
@objc protocol LocalService {}
class LocalServiceProvider: LocalService {}
let sp = LocalServiceProvider()
guard let remoteService: RemoteService = await rendezvous(machPortName: "com.example.foo", value: sp as LocalService) else {
return
}
How close can I actually get to this idealized version?