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