I am attempting to understand the expected behavior of several points related to XPC. Mainly, I have the following questions:
- Should I expect that NSXPCConnection.remoteObjectProxyWithErrorHandler to call the error handler if the associated NSXPCListenerDelegate returns false in its listener function?
- Does the error handler get called immediately if the remoteObjectProxyWithErrorHandler function fails?
- What does remoteObjectProxy return if an actual proxy object is never exported on the service-side (in the listener function)?
- Should I expect that NSXPCConnection.invalidate() and/or the connection's invalidationHandler to be called when the associated NSXPCListenerDelegate returns false in its listener function?
According to the listener documentation below, it appears the listener function is supposed to invalidate the connection; however, I am not seeing this be the case.
https://developer.apple.com/documentation/foundation/nsxpclistenerdelegate/1410381-listener
To reject the connect, return a value of false. This causes the connection object to be invalidated.
I have written a test that exhibits the fact when the listener function returns false, rather than the invalidationHandler being called, the interruptionHandler is called.
import XCTest
final class InvalidateTest: XCTestCase {
func testConnectionIsInvalidatedOnListenerRejection() {
//set up anonymous XPC Listener
let listener = NSXPCListener.anonymous()
let listenerDelegate = MockListenerDelegate()
listener.delegate = listenerDelegate
listener.resume()
//establish connection to service
let clientConnection = MockConnection(listenerEndpoint: listener.endpoint)
var interruptHandlerCalled = false
var invalidateHandlerCalled = false
let interruptionHandler = { interruptHandlerCalled = true }
let invalidationHandler = { invalidateHandlerCalled = true }
clientConnection.interruptionHandler = interruptionHandler
clientConnection.invalidationHandler = invalidationHandler
clientConnection.remoteObjectInterface = NSXPCInterface(with: XPCServDelegate.self)
clientConnection.exportedInterface = NSXPCInterface(with: XPCCliDelegate.self)
clientConnection.exportedObject = XPCCDelegate()
clientConnection.resume()
// get the proxy delegate to make the XPC pulse call
guard let proxy = clientConnection.remoteObjectProxy() as? XPCServDelegate else {
XCTAssert(false, "Unable to get proxy object")
return
}
// why is it not failing to get a proxy object in this case?
// make the pulse call
var replyCalled = false
let semaphore = DispatchSemaphore(value: 0)
proxy.pulse(reply: {
replyCalled = true
semaphore.signal()
})
let waitResult = semaphore.wait(timeout: .now() + 1)
XCTAssertEqual(waitResult, .timedOut)
XCTAssertFalse(replyCalled)
// why do these assertions fail?
XCTAssertTrue(clientConnection.invalidateCalled)
XCTAssertTrue(invalidateHandlerCalled)
XCTAssertFalse(interruptHandlerCalled)
}
}
@objc public protocol XPCServDelegate {
func pulse(reply: @escaping () -> Void)
}
class XPCSDelegate : XPCServDelegate {
func pulse(reply: @escaping () -> Void) {
reply()
}
}
@objc public protocol XPCCliDelegate {}
class XPCCDelegate : XPCCliDelegate {}
fileprivate class MockConnection : NSXPCConnection {
var invalidateCalled = false
override func invalidate() {
invalidateCalled = true
super.invalidate()
}
}
fileprivate class MockListenerDelegate : NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
return false
}
}