A Swift library for Mach-based Inter-Process Communication (IPC) on Darwin systems (macOS, iOS, etc.). MachIPC provides a high-level, type-safe API for sending and receiving messages between processes using Mach ports.
This project is work in progress. The API may change and features may be incomplete.
- Extensive tests
- Proper benchmarks
- Add secure layer
- RPC layer
- Flatbuffer convenience layer
- Type-safe messaging 🔐: Protocol-based message system with
MachPayloadProviderandMachMessageConvertible - Local and remote communication 🌐: Automatically handles both in-process and cross-process messaging
- Bootstrap service integration: Uses Mach bootstrap services for service discovery
- Zero-copy local optimization: Direct message passing for in-process communication
- Logger support: Optional logging for debugging and monitoring
- Swift concurrency ready: Built with
Sendableconformance for modern Swift
MachIPC is optimized for high-throughput messaging:
- Up to 1,000,000 messages per second on a single core
- ~1 μs per message latency — comparable to single-process performance (standard
DispatchQueue.asynctakes ~2.5 μs) - At least 2x faster than XPC for message passing
These benchmarks demonstrate the efficiency of direct Mach message passing compared to higher-level IPC frameworks.
A separate secure layer is planned for future releases to provide end-to-end encryption for sensitive communications.
- Swift 5.4 or later
- macOS, iOS, or other Darwin-based systems
Add MachIPC to your Package.swift:
dependencies: [
.package(url: "https://github.com/MaximKotliar/MachIPC", branch: "master")
]Or add it through Xcode:
- File → Add Package Dependencies...
- Enter the repository URL
- Select the master branch
import MachIPC
// Create a host that listens for messages
let host = try MachHost<String>(
endpoint: "com.example.service",
onReceive: { message in
print("Received message: \(message)")
}
)
// Keep the host alive (e.g., in your app's lifecycle)import MachIPC
// Create a client to send messages
let client = try MachClient<String>(endpoint: "com.example.service")
// Send a message
try client.sendMessage("Hello, Mach IPC!")For custom types, you can either:
- Use
MachMessageConvertible(recommended for most cases):
struct MyMessage: MachMessageConvertible {
let id: Int
let data: String
init(machPayload: Data) {
// Deserialize from Data
let decoder = JSONDecoder()
self = try! decoder.decode(MyMessage.self, from: machPayload)
}
var machPayload: Data {
// Serialize to Data
let encoder = JSONEncoder()
return try! encoder.encode(self)
}
}- Or implement
MachPayloadProviderdirectly for maximum performance:
struct MyMessage: MachPayloadProvider {
let id: Int
let data: String
init(machPayloadBuffer: UnsafeRawPointer, count: Int) {
// Deserialize directly from buffer
let data = Data(bytes: machPayloadBuffer, count: count)
let decoder = JSONDecoder()
self = try! decoder.decode(MyMessage.self, from: data)
}
func withPayloadBuffer<T>(_ body: (UnsafeRawPointer, Int) throws -> T) rethrows -> T {
// Serialize to buffer
let encoder = JSONEncoder()
let data = try encoder.encode(self)
return try data.withUnsafeBytes { bytes in
try body(bytes.baseAddress!, bytes.count)
}
}
var payloadCount: Int {
let encoder = JSONEncoder()
return (try? encoder.encode(self).count) ?? 0
}
}- Automatic support for
Codabletypes:
struct MyMessage: Codable {
let id: Int
let data: String
}
// Automatically conforms to MachMessageConvertible via JSON encodingUse with MachHost and MachClient:
let host = try MachHost<MyMessage>(
endpoint: "com.example.service",
onReceive: { message in
print("Received: \(message)")
}
)
let client = try MachClient<MyMessage>(endpoint: "com.example.service")The logging system is designed to be flexible: conform to the Logger protocol for custom logging implementations, or use the provided ConsoleLogger for simple print-based logging.
// Use the provided ConsoleLogger for simple prints
let logger = ConsoleLogger()
var configuration = MachHostConfiguration.default
configuration.logger = logger
configuration.logsThroughput = true // Enable throughput logging
configuration.highPerformanceModeThreshold = 200_000 // Messages per second
let host = try MachHost<String>(
endpoint: "com.example.service",
configuration: configuration,
onReceive: { message in
print("Received: \(message)")
}
)
let client = try MachClient<String>(endpoint: "com.example.service", logger: logger)
// Or implement your own Logger
struct MyLogger: Logger {
func log(_ level: Int32, _ message: String) {
// Custom logging implementation
}
}MachHost can be configured using MachHostConfiguration:
var configuration = MachHostConfiguration.default
configuration.logger = ConsoleLogger() // Optional logger
configuration.bufferSize = 1024 * 512 // Increase for larger messages (default: 256KB)
configuration.logsThroughput = true // Log messages per second
configuration.highPerformanceModeThreshold = 200_000 // Switch to no-wait mode at this throughput
configuration.threadPriority = 10 // Higher priority for lower latency (default: 0)
let host = try MachHost<String>(
endpoint: "com.example.service",
configuration: configuration,
onReceive: { message in
print("Received: \(message)")
}
)Configuration options:
logger: Optional logger for debugging (default:ConsoleLogger())bufferSize: Buffer size for receiving messages (default: 256KB)logsThroughput: Enable throughput logging every second (default:false)highPerformanceModeThreshold: Messages per second threshold to switch to high-performance mode (default: 200,000)threadPriority: Thread priority for receiver thread (0 = normal, higher = higher priority, range: -127 to 127, default: 0)
-
MachHost<Message>: Receives messages on a registered endpoint- Registers with Mach bootstrap service for remote access
- Uses a dedicated receiver thread for high-performance message reception
- Configurable via
MachHostConfiguration(logger, buffer size, throughput logging, performance thresholds) - Automatically switches to high-performance mode (no-wait) at configurable throughput threshold (default: 200k msg/s)
- Supports local in-process message passing for performance
-
MachClient<Message>: Sends messages to a registered endpoint- Automatically resolves local or remote endpoints
- Uses Mach message passing for cross-process communication
- Falls back to direct in-process calls when possible
-
MachPayloadProvider: Lowest-level protocol for raw buffer access- Direct buffer access for maximum performance
- Used by
MachHostandMachClientfor message handling
-
MachMessageConvertible: Data-level protocol extendingMachPayloadProvider- Provides serialization/deserialization to/from
Data - Built-in support for
DataandStringtypes - Automatic support for all
Codabletypes via JSON encoding
- Provides serialization/deserialization to/from
-
MachLocalhostRegistry: Manages in-process endpoint registry- Enables zero-copy local message passing
- Thread-safe endpoint lookup
-
Local (in-process):
- Client automatically resolves if host is in the same process via local registry
- Direct function call to host's
onReceivehandler - Zero-copy message passing
- Note: Local resolution can be disabled via
MachLocalhostRegistry.shared.isLookupEnabled
-
Remote (cross-process):
- Client looks up endpoint via bootstrap service
- Message payload accessed via
withPayloadBufferand sent via Mach message - Host receives message via dedicated receiver thread
- Message constructed directly from raw buffer using
init(machPayloadBuffer:count:) - Message passed to
onReceivecallback
Run all tests:
swift testRun benchmarks:
swift test --filter BenchmarkTestsThis project is licensed under the MIT License.
Contributions are welcome! Please note that this project is work in progress, so expect API changes.