Can I express this abstraction (XPC rendezvous) with Swift's generics?

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:

  1. exportedObject - this is value
  2. exportedInterface - a wrapper around an ObjectiveC.Protocol that value conforms to. In other words, an @objc protocol. This means that even though exportedObject is an Any, it actually has to be a reference type.
  3. 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?

1 Like