Matthew Miner

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.

Xcode Build Phases

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:

Xcode Build Settings

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.


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