Creating an XPC Service in Swift
Say you want to add an XPC Service to your Swift app. Fine idea, but notice that Xcode spits out Objective-C when you add an XPC Service target.1 Nobody has time for that. Fortunately converting Xcode’s starter code to Swift is mostly straightforward. Let’s twiddle some knobs and twist a few dials and get you rolling.
After creating an XPC Service target, here named “MyService”, we have four files: main.m, MyService.h, MyService.m, and MyServiceProtocol.h. Rename these to main.swift, MyService.swift, MyServiceDelegate.swift, and MyServiceProtocol.swift. Next, add them to the target’s “Compile Sources” build phase.
Now replace the Objective-C code in each file with its Swift translation.
// main.swift
import Foundation
let delegate = MyServiceDelegate()
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
// MyService.swift
import Foundation
class MyService: NSObject, MyServiceProtocol {
func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void) {
let response = string.uppercased()
reply(response)
}
}
// MyServiceDelegate.swift
import Foundation
class MyServiceDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
let exportedObject = MyService()
newConnection.exportedInterface = NSXPCInterface(with: MyServiceProtocol.self)
newConnection.exportedObject = exportedObject
newConnection.resume()
return true
}
}
// MyServiceProtocol.swift
import Foundation
@objc public protocol MyServiceProtocol {
func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void)
}
Before compiling this code we need to tweak build settings:
- Install Objective-C Compatibility Header: NO
- Objective-C Generated Interface Header Name: “”
- Runtime Search Paths: @loader_path/../../../../Frameworks
- Swift Language Version: whatever version of Swift you use
With any luck we can now build. In our main application we delegate a task to our service by creating an NSXPCConnection
with the name of the target’s bundle identifier then calling the functions defined in MyService
.
import MyService
...
let connection = NSXPCConnection(serviceName: "com.matthewminer.MyService")
connection.remoteObjectInterface = NSXPCInterface(with: MyServiceProtocol.self)
connection.resume()
let service = connection.remoteObjectProxyWithErrorHandler { error in
print("Received error:", error)
} as? MyServiceProtocol
service?.upperCaseString("hello XPC") { response in
print("Response from XPC service:", response)
}
That’s it! For reference you can find the above code snippets in this Gist. Boy howdy.
Thanks to GitHub user adur1990 for Swift 4 compatibility fixes.
As of Xcode 9, at least. I hope Apple changes this in future versions and this bone dry article can be tossed into the World Wide Web’s trash bin. ↩