© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
V. VashurkinmacOS Daemonologyhttps://doi.org/10.1007/978-1-4842-7277-0_13

13. Pass Objects by Proxy: The Callable XPC Objects

Volodymyr Vashurkin1  
(1)
Dnipro, Ukraine
 

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:
  • Progress reporting

  • Delegating functionality to another process

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-1

Introducing 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-2

Example: 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-3

Example: 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.174.174