XPC over network?

The NSConnection and NSDistantObject headers say to use NSXPCConnection instead, but NSXPCConnection doesn't have an init method that takes a port. So what's the best way to send messages between 2 remote apps over a network?


We have apps that do work, and users want apps to monitor the workers' progress. The old way we did this was with NSSocketPort, NSConnection, NSDistantObject<OurCustomProtocol>, then doing the whole NSMethodSignature, NSInvocation dance in the monitor app, and NSThread, NSTimer, and other objects in the worker to periodically send out progress updates to any listening monitors. The monitor also periodically sends out "are you there" messages to see if the worker has quit/crashed so the monitor can reset the progress display.


How would this be done without deprecated classes like NSConnection?

Replies

XPC does not support IPC across a network connections.

WARNING It is not safe to use Distributed Objects (DO) — that is,

NSConnection
and friends — across security domains, and that obviously includes the network. This is one of the main reasons that DO was deprecated.

So what's the best way to send messages between 2 remote apps over a network?

My recommendation is that you use standard networking APIs for this. The networking stack does not contain anything as simple as

NSXPCConnection
, but there are a number of features that you can use to greatly simplify the problem:
  • If you’re using Swift, you can use

    Codable
    to serialise and deserialise your data structures.
  • Alternatively, you can use

    NSSecureCoding
    .
  • Network framework makes it easy to listen for and accept TCP connections.

  • In macOS 10.15 beta we’ve added support for framing to the Network framework, meaning you can work in terms of messages rather than byte streams. Alas, on 10.14 you’ll have to implement this framing yourself.

  • The Network framework supports TLS out of the box, including pre-shared key (PSK) TLS if you need to operate peer-to-peer.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Blarg. Well, since the data being transmitted is nowhere near sensitive, and is internal to our network, and since we probably won't get to use 10.15 until, oh, 2022, I think we'll continue using what we have until it actually goes away.


Thanks, Quinn.

Well spit on a sparkplug. The existing code is crashing on 10.13.6 when it tries to get the rootProxy of the NSConnection and the remote server app isn't running, but only the 2nd time we call this, due to an over-release:


NSDistantObjectTableEntry object 0x604000052510 overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug


So I started looking ino the Network framework, but I can't use that on 10.13.6. Arg! We're a print company, so we're constantly years behind on the OS.

The existing code is crashing on 10.13.6 when it tries to get the rootProxy of the NSConnection and the remote server app isn't running, but only the 2nd time we call this, due to an over-release

Indeed. Earlier I wrote:

This is one of the main reasons that DO was deprecated.

The other main reason that DO was deprecated is that it has bugs, and the DO architecture makes it very hard for us to fix those bugs (fixing a bug like this one perturbs the system such that a new bug pops up in its place)-:

So I started looking ino the Network framework, but I can't use that on 10.13.6.

OK. Still, it’s not like pre-10.14 systems don’t have networking APIs (-: but those APIs are definitely not as nice. Fortunately, the networking aspect of this problem is not the hard part. Writing code that transports messages are a TCP connection is relatively straightforward, and if you wrote it today you could put that code behind an abstraction layer, making it easy to switch to Network framework in the future. My TCPTransports sample code shows one way to approach this.

Once you have that, you can tackle the hard part of this problem, that is, marshalling your existing DO calls on to your message abstraction. Fortunately, you can write that code once now, and then switch the message abstraction out from underneath it in the future.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I've been reading through TCPTransports (and trying to grok Swift in the process - seems like I never have time to sit down and learn yet another language) and Apple docs for things like NSURLSession. So, I can understand how our monitor app can connect to our known server[s] at known IP addresses using a specific port, possibly using an NSURLSessionStreamTask. But what I don't yet understand is how the server app knows how to communicate with that port, either listening for incoming requests for status updates from a monitor app, or to voluntarily send out status messages to any listening monitor apps. Pretend I'm dumb as mud about all this. (And hey, I am! I didn't write the original NSDistancObject stuff, so it's all new to me.)

But what I don't yet understand is how the server app knows how to communicate with that port …

Indeed. TCPTransports shows the client side of this but does not include the server side. And the server side is less than ideal prior to the introduction of the Network framework. Specifically, the nicest client-side API,

NSURLSessionStreamTask
, has no server-side equivalent )-: So, you have a couple of options here:
  • You can use different APIs on the client and server sides (A).

  • You can use the same API on both sides, at the cost of abandoning

    NSURLSessionStreamTask
    (B).

My preference is for B, and the API I usually choose is

NSStream
.

There’s a bunch of sample code showing how to use

NSStream
for networking, and you’re in luck in that I’ve never got around to updating it all for Swift (-: A good place to start is RemoteCurrency. This is quite old, but it shows a lot of the core concepts. The only change that you really need to make is to delete the
QServer
class and do the listen/accept dance using
NSNetService
. A good place to see that code is the WiTap sample code.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks very much, Quinn. Again! And we hear that some new servers are coming soon, so 10.14 isn't too far away. Now if we can just get them to update our MacBooks at the same time.