In most cases, passing objects by copy is enough when exchanging data over XPC. Passing by copy is pretty straightforward, simple, and obvious and should be preferred when dealing with the NSXPC API.
But in some cases, passing by copy is not the way you want to communicate over XPC. In some cases, you want to pass some entities over a connection and use them as the usual
callable objects, not like structs. The following are two examples:
The downside to passing objects by proxy is the performance penalty. It occurs because every access to the object requires interprocess communication.
For this reason, you should pass objects by proxy only if it is not possible to pass them by copy.
Proxy objects are configured in a similar way to dealing with the
remoteObjectInterface property of the initial connection.
@objc(DAEDemoXPCService)
protocol DemoXPCService {
// Passing object by proxy.
func download(resource: String, delegate: DownloadDelegate) -> Void
}
@objc(DAEDownloadDelegate)
protocol DownloadDelegate {
func downloadDidStart()
func downloadDidUpdateProgress(downloaded: Int, total: Int)
func downloadDidFinish(error: NSError?)
}
NSXPCInterface for the initial
NSXPCConnection should be extended, as shown in Listing
13-1.
let interface = NSXPCInterface(with: DemoXPCService.self)
// Describe proxy object interface
let delegate = NSXPCInterface(with: DownloadDelegate.self)
interface.setInterface(
delegate,
for: #selector(DemoXPCService.download(resource:delegate:)),
argumentIndex: 1, // the second parameter of
ofReply: false // download(resource:delegate:) method
)
Listing 13-1Introducing the Interface for the XPC Proxy Object
After that, it is safe to pass any object of type
NSObject that conforms to
DownloadDelegate over an XPC connection. See Listing
13-2.
// Client side
private class Client: NSObject {
// ...
func download() {
let connection = NSXPCConnection(serviceName: "com.daemonology.DemoAdvancedXPC")
let iface = NSXPCInterface(with: DemoXPCService.self)
connection.remoteObjectInterface = iface
connection.resume()
let proxy = connection.remoteObjectProxy as! DemoXPCService
proxy.download(resource: "this.book", delegate: self)
}
}
extension Client: DownloadDelegate {
// ...
func downloadDidStart() {
print("Download started")
}
func downloadDidUpdateProgress(downloaded: Int, total: Int) {
print("Downloaded (downloaded) from (total)")
}
func downloadDidFinish(error: NSError?) {
print("Download finished. Error: (error).")
}
}
// Service side
func download(resource: String, delegate: DownloadDelegate) {
let parts = 5
DispatchQueue.global().async {
delegate.downloadDidStart()
for i in 1...parts {
sleep(1)
delegate.downloadDidUpdateProgress(downloaded: i, total: parts)
}
delegate.downloadDidFinish(error: nil)
}
}
Listing 13-2Example: Passing an Object by Proxy
NSProgress: Alternate Observation Mechanism Over XPC
Usually when dealing with XPC, we use asynchronous methods that return Void and provide results in their reply blocks.
macOS 10.13 changes this rule. Since 10.13, it is legal to return an
NSProgress instance from the methods used by XPC as a return value with no additional setup, as shown in Listing
13-3.
@objc(DAEServiceInterface)
protocol ServiceInterface {
func count(
to n: Int,
delay: TimeInterval,
completion: @escaping () -> Void
) -> Progress
}
// Client side
let connection = NSXPCConnection(serviceName: "com.daemonology.DemoAdvancedXPC")
let iface = NSXPCInterface(with: ServiceInterface.self)
connection.remoteObjectInterface = iface
connection.resume()
let proxy = connection.remoteObjectProxy as! ServiceInterface
let progress = proxy.count(to: 5, delay: 1) {
print("Completed")
}
token = progress.observe(.completedUnitCount) { p, _ in
print("(p.completedUnitCount) of (p.totalUnitCount)")
}
// output
// 1 of 5
// 2 of 5
// 3 of 5
// 4 of 5
// 5 of 5
// Completed
Listing 13-3Example: Returning NSProgress
from XPC Method
Summary
In this chapter, we covered the most common and widespread approaches of XPC communication: passing objects by copy, passing objects by proxy, and reporting using NSProgress over XPC.
But there is one thing we have still not discussed: how to pass one XPC endpoint to another to allow direct communication between them. The next chapter is about that.