Bridging the Divide: How Swift's C++ Interop Unlocks Performance and Code Reuse
Share this article
The chasm between Swift's modern safety and C++'s raw performance has long been a barrier for Apple platform developers. With Swift 5.9's stabilized C++ interoperability, that gap collapses—letting engineers harness decades of battle-tested C++ libraries without sacrificing Swift's expressiveness. Through a hands-on ATM implementation, we'll dissect this transformative capability.
The Cross-Language Blueprint
At the project's core lies a deliberate separation:
├── ATMProj (SwiftUI app)
│ ├── View Models
│ ├── UI Components
└── cpp (C++ core)
├── CMakeLists.txt
├── Package.swift
└── Sources
├── C++ ATM logic
└── Swift wrappers
The cpp directory houses the financial logic as a CMake-built static library, while SwiftPM orchestrates integration via a unified manifest. This structure ensures iOS/macOS apps and CLI tools share identical business logic.
C++ Core: Performance-Critical Foundations
The ATM's logic exemplifies clean C++ encapsulation:
// ATM.h
#pragma once
class ATM {
public:
ATM(int initialBalance);
bool withdraw(int amount);
int getBalance() const;
private:
int balance;
};
// ATM.cpp
#include "ATM.h"
bool ATM::withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
return true;
}
return false;
}
CMake builds this into a static library, while ATMWithdrawCpp.h re-exposes headers for Swift consumption. The critical build configuration enforces C++17:
target_include_directories(ATMWithdrawCpp PUBLIC ${HEADER_PATH})
set_target_properties(ATMWithdrawCpp PROPERTIES CXX_STANDARD 17)
Swift Integration: Bridging with Safety
The magic unfolds in Swift's interoperability layer. Notice how ATMWrapper creates a Swifty interface for the C++ class:
import ATMWithdrawCpp
public struct ATMWrapper {
private var underlying: ATM // Direct C++ instance
public init(initialBalance: Int32) {
underlying = ATM(initialBalance)
}
public mutating func withdraw(amount: Int32) -> Bool {
return underlying.withdraw(amount)
}
}
SwiftPM's manifest is configured for cross-language harmony:
// Package.swift
targets: [
.target(
name: "ATMWithdrawCpp",
publicHeadersPath: "include"
),
.target(
name: "ATMPackage",
dependencies: ["ATMWithdrawCpp"],
swiftSettings: [.interoperabilityMode(.Cxx)]
)
]
The .interoperabilityMode(.Cxx) flag is non-negotiable—it activates Swift's C++ bridging capabilities.
From CLI to SwiftUI: Shared Logic in Action
The same ATMWrapper powers both a terminal interface:
// CLI
var atm = ATMWrapper(initialBalance: 1000)
if atm.withdraw(amount: amount) {
print("✅ Withdrawn \(amount)")
}
And a reactive SwiftUI application:
// ViewModel
class ATMViewModel: ObservableObject {
@Published var balance: Int32
private var atm: ATMWrapper
func withdraw(amount: String) {
guard let value = Int32(amount),
atm.withdraw(amount: value) else { return }
balance = atm.getBalance()
}
}
// SwiftUI View
Button("Withdraw") {
viewModel.withdraw(amount: input)
}
Xcode integration requires one critical build setting: -cxx-interoperability-mode=default under "Other Swift Flags".
The New Frontier: Write Once, Run Anywhere
This interoperability transcends Apple's ecosystem. The C++ ATM core compiles anywhere—Linux, Windows, embedded systems—while Swift wrappers adapt it to local conventions. SwiftPM and CMake automate cross-platform builds, eliminating logic duplication. As Swift's C++ support matures, it threatens the raison d'être of many cross-platform frameworks by enabling true native development with shared C++ underpinnings.
Developers now face a compelling choice: rewrite performance-critical C++ in Swift, or seamlessly integrate existing libraries while writing modern UI layers. For resource-intensive applications, the latter may prove irresistible.
Source: Artur Gruchała, Swift and C++ Interoperability in Practice