FlowBridge is a cross-language bridge for signals and slots between C++, Objective-C, and Swift. Type-safe events in C++ and convenient hooks in Swift.
- Type-safe signals in C++
- Automatic bridging to Swift / Objective-C
- Pass values (string, number, bool, binary data) between layers
- No boilerplate callbacks β event-driven design
Add FlowBridge as a Swift Package dependency:
dependencies: [ .package(url: "https://github.com/danrom11/FlowBridge.git", from: "2.1.0") ]
FlowBridge revolves around signals (events you define in C++) and slots (handlers you connect in Swift/Objective-C).
Use the FLOW_SIGNAL macro to declare a named signal:
#include "FlowBridge.hpp" FLOW_SIGNAL(tick_score);
iοΈ You donβt strictly need to register a signal β emitting will create it automatically. But calling FlowBridge::registerSignal(tick_score) is recommended for clearer architecture and to document the intent.
Emit an event whenever you want:
FlowBridge::emit(tick_score, "Hello from C++");
You can pass strings, numbers, booleans, or even binary data
In Swift, subscribe by name and handle the payload:
import FlowBridge struct FlowSignals { static let tick_score = "tick_score" } FlowBridge.connect(FlowSignals.tick_score) { data in if let text = data as? String { print("Received:", text) } }
Or you can use a selector:
import FlowBridge struct FlowSignals { static let tick_score = "tick_score" } @objc func test(_ data: Any?) { print("Data: \(data ?? "none")") if let stringData = data as? String { DispatchQueue.main.async { self.nameLabel.text = stringData } } } override func viewDidLoad() { super.viewDidLoad() nameLabel.text = "0" FlowBridge.connect(FlowSignals.tick_score, target: self, slot: #selector(test(_:))) let manager = ManagerWrapper.shared manager().start(withInterval: 1000) }
Starting from version 2.1.0, FlowBridge supports automatic serialization/deserialization of complex data types (dictionaries, arrays, nested structures) using TLV (Type-Length-Value).
C++ side:
#include "FlowBridge.hpp" #include "tlv.hpp" // Create a document structure tlv::Map document{ {"user_id", int64_t(42)}, {"name", std::string("Alice")}, {"role", std::string("developer")}, {"skills", tlv::Array{ std::string("C++"), std::string("Swift"), std::string("Objective-C") }}, {"active", true}, {"rating", 4.8} }; // Emit signal with TLV-encoded data FlowBridge::emit("user_profile", tlv::encode(tlv::Value(document)));
Swift side:
import FlowBridge FlowBridge.connect("user_profile") { data in // Receive automatically decoded dictionary if let profile = data as? [String: Any] { let userId = profile["user_id"] as? Int ?? 0 let name = profile["name"] as? String ?? "" let role = profile["role"] as? String ?? "" let skills = profile["skills"] as? [String] ?? [] let active = profile["active"] as? Bool ?? false let rating = profile["rating"] as? Double ?? 0.0 print("User: \(name) (ID: \(userId))") print("Role: \(role)") print("Skills: \(skills.joined(separator: ", "))") print("Active: \(active), Rating: \(rating)") } }
Swift side:
import FlowBridge // Send a request with parameters FlowBridge.emit("api_request", data: [ "endpoint": "/users", "method": "GET", "params": [ "page": 1, "limit": 20, "sort": "name" ], "headers": [ "Authorization": "Bearer token123", "Accept": "application/json" ] ])
C++ side:
#include "FlowBridge.hpp" #include "tlv.hpp" // Connect to signal with TLV handling static FlowBridge::Connection connection = FlowBridge::connect<std::vector<uint8_t>>("api_request", [](const std::vector<uint8_t>& buffer) { try { tlv::Value value = tlv::decode(buffer); // Check if we received a dictionary if (auto request = std::get_if<tlv::Map>(&value)) { // Extract endpoint auto endpoint_it = request->find("endpoint"); if (endpoint_it != request->end()) { if (auto endpoint = std::get_if<std::string>(&endpoint_it->second)) { std::cout << "Endpoint: " << *endpoint << std::endl; } } // Extract parameters auto params_it = request->find("params"); if (params_it != request->end()) { if (auto params = std::get_if<tlv::Map>(¶ms_it->second)) { auto page_it = params->find("page"); if (page_it != params->end()) { if (auto page = std::get_if<int64_t>(&page_it->second)) { std::cout << "Page: " << *page << std::endl; } } } } } } catch (const std::exception& e) { std::cerr << "Failed to decode TLV: " << e.what() << std::endl; } });
| Swift type | C++ type | TLV representation |
|---|---|---|
nil |
nullptr |
null |
Bool |
bool |
boolean |
Int |
int64_t |
integer |
Double |
double |
float |
String |
std::string |
string |
Data |
std::vector<uint8_t> |
binary |
Array |
tlv::Array |
array |
Dictionary |
tlv::Map |
map |
- Type safety β data preserves its types during transmission
- Arbitrary nesting β dictionaries can contain arrays that contain other dictionaries
- Automatic deserialization β data arrives in Swift ready to use
- Efficiency β compact binary representation
A full demo project is available in https://github.com/danrom11/FlowBridge-Examples