Swiftpack.co - Alkenso/sXPC as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by Alkenso.
Alkenso/sXPC 0.3.4
Use power of Swift to make NSXPCConnection type-bound
⭐️ 18
🕓 13 weeks ago
macOS
.package(url: "https://github.com/Alkenso/sXPC.git", from: "0.3.4")

sXPC - Swift-typed wrapper around NSXPCConneciton

sXPC allows you to

  • make NSXPCConnection to produce typed remoteObject / set typed exportedObject
  • pass Swift-specific structs/enums over XPC connection (with very little additional code)
  • have NSXPCIntegrace description in the single place
  • hide objective-c details, using pure Swift in the App

XPCTransport

XPCTransport is kind of add-on to sXPC, providing message-oriented approach to XPC communication XPCTransport introduces sort of stable connection between client and service endpoints

  • built-in handshake with initial customizable payload
  • built-in reconnect behaviour
  • Codable types support
  • Bidirectional communication

Library family

You can also find Swift libraries for macOS / *OS development

  • SwiftSpellbook: Swift common extensions and utilities used in everyday development
  • sLaunchctl: Swift API to register and manage daemons and user-agents
  • sMock: Swift unit-test mocking framework similar to gtest/gmock

XPC service example

Note: full sample code resides in Sample folder

Assume protocol TokenService you are going to use over XPC connection

public struct TokenRequest: Equatable, Codable {
    public var user: String
    public var password: String
}

public struct TokenResponse: Equatable, Codable {
    public var token: String
    public var expiration: Date
}

public protocol TokenService {
    func requestToken(_ request: TokenRequest, reply: @escaping (Result<TokenResponse, Error>) -> Void)
}

Create connection to TokenService and request token

let connection = TokenServiceXPCConnection(xpc: .service("com.alkenso.XPCService"))
connection.resume()

let proxy = connection.remoteObjectProxy { error in
    print(error)
}

let request = TokenRequest(user: "alkenso", password: "1234567_lol")
proxy.requestToken(request) {
    print($0)
}

Setup TokenService listener

// Define TokenService implementation
struct MockTokenService: TokenService { ... }

// Start listener
let listener = CreateServiceXPCListener(listener: listener)
listener.newConnectionHandler = {
    $0.exportedObject = MockTokenService()
    $0.resume()
    return true
}
listener.resume()

XPCTransport example

Note: full sample code resides in Sample folder

Assume we need to achieve two pretty common, close to real-world scenarios:

  1. Receiving remote notifications
  2. Sending analytic events

The logic of receiving/sending is implemented in another process and we can connect it via XPC

// XPCService -> App
public enum RemoteNotification: Codable {
    case notify(String)
    
    // first generic parameter - approval text
    // second generic parameter - Boolean, indicating request is approved by user
    case askApproval(XPCTransportMessage<String, Bool>)
}

// App -> XPCService
public struct AnalyticEvent: Codable {
    var reason: String
    var date: Date
}

Create connection to XPCService

// create XPCTransport connection
let connection = XPCTransportConnection(xpc: .service("com.alkenso.XPCService"))

// setup incoming messages handler
connection.setReceiveMessageHandler(RemoteNotification.self) { 
    switch $0 {
        case .notify(let text):
            print("[Notifcation] \(text)")
        case .askApproval(let message):
            print("[Approval] \(message.request)")
            
            // Reply to XPCTransportMessage
            message.reply(.success(true))
        }
    }
}

// receive connection state updates (react to reconnects)
connection.stateHandler = { print("Connection state: \($0)") }

// all handlers/replies will be called on this queue
connection.queue = .main

// activate connection
connection.activate()

// send analytic event through connection
do {
    try connection.send(AnalyticEvent(reason: "Button clicked", date: Date()))
} catch {
    print("Failed to send analytic event. Error: \(error)")
}

Setup XPCTransport server

// create XPCTransport server
let server = XPCTransportServer(.service)

// setup incoming messages handler
server.setReceiveMessageHandler(AnalyticEvent.self) { _, event in
    print("Analytic event: \(event)")
}

// activate server
server.activate()

// ... some time later
// send notifications to all connected clients
guard let peer = server.activeConnections.first else { return }
do {
    // send simple notification
    try server.send(to: peer, message: .notify("You have new message"))
    
    // send approval request that requires reply from the client(s)
    let message = XPCTransportMessage<String, Bool>(
        request: "New sign in detected on another device. Allow it?",
        reply: { approvalResult in
            switch approvalResult {
            case .success(let approved):
                print("Approved: \(approved)")
            case .failure(let error):
                print("Approval failed: \(error)")
            }
        }
    )
    try server.send(to: peer, message: .askApproval(message))
} catch {
    print("Failed to send notification to peer. Error: \(error)")
}

GitHub

link
Stars: 18
Last commit: 13 weeks ago
Advertisement: IndiePitcher.com - Cold Email Software for Startups

Dependencies

Release Notes

Adopt latest SwiftSpellbook, bump min macOS to 10.15
13 weeks ago

Bump min supported macOS to 10.15

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics