Swift concurrency support for NSXPCConnection
Features:
- structured concurrency extensions for
NSXPCConnection
- convenience callbacks for better interfacing with Objective-C-based XPC protocols
RemoteXPCService
for easier type-safetyQueuedRemoteXPCService
for message ordering control
You might be tempted to just make your XPC interface functions async. While the compiler does support this, it is very unsafe. This approach does not handle connection failures and will results in hangs.
Given an XPC service like this:
@objc
protocol XPCService {
func method()
func errorMethod(reply: (Error?) -> Void)
func valueAndErrorMethod(reply: (String?, Error?) -> Void)
func dataAndErrorMethod(reply: (Data?, Error?) -> Void)
}
You can use NSXCPConnection
directly:
let conn = NSXPCConnection()
conn.remoteObjectInterface = NSXPCInterface(with: XPCService.self)
// access to the underlying continuation
try await conn.withContinuation { (service: XPCService, continuation: CheckedContinuation<Void, Error>) in
service.errorMethod() {
if let error = $0 {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
}
}
try await conn.withService { (service: XPCService) in
service.method()
}
try await conn.withErrorCompletion { (service: XPCService, handler) in
service.errorMethod(reply: handler)
}
let value = try await conn.withValueErrorCompletion { (service: XPCService, handler) in
service.valueAndErrorMethod(reply: handler)
}
let decodedValue = try await conn.withDecodingCompletion { (service: XPCService, handler) in
service.dataAndErrorMethod(reply: handler)
}
You can also make use of the RemoteXPCService
type, which will remove the need for explicit typing of the service.
let conn = NSXPCConnection()
let remote = RemoteXPCService<XPCService>(connection: conn, remoteInterface: XPCService.self)
let decodedValue = try await remote.withDecodingCompletion { service, handler in
service.dataAndErrorMethod(reply: handler)
}
The QueuedRemoteXPCService
type is very similar to RemoteXPCService
, but offers a queuing interface to control the ordering of message delivery. This is done via the AsyncQueuing
protocol, for flexible, dependency-free support. If you need a compatible queue implementation, check out Queue. And, if you know of another, let me know so I can link to it.
import AsyncXPCConnection
import Queue
extension AsyncQueue: AsyncQueuing {}
let queue = AsyncQueue()
let connection = NSXPCConnection()
let queuedService = QueuedRemoteXPCService<XPCService, AsyncQueue>(queue: queue, provider: { connection })
queuedService.addOperation { service in
service.method()
}
let value = try await queuedService.addDecodingOperation { service, handler in
service.dataAndErrorMethod(reply: handler)
}
I would love to hear from you! Issues or pull requests work great. Both a Matrix space and Discord are available for live help, but I have a strong bias towards answering in the form of documentation. You can also find me on mastodon.
I prefer collaboration, and would love to find ways to work together if you have a similar project.
I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace.
By participating in this project you agree to abide by the Contributor Code of Conduct.