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);βΉοΈ 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