Skip to content

Swift: provide & consume

The Swift API mirrors the Kotlin one. Every entry point goes through BridgeKitRuntime.default — the shared singleton that installs itself as the BridgeKitNative delegate at app init.

import BridgeKit // runtime: BridgeKitRuntime, Binding, BridgeValue, Scope, BridgeKitError
import AxionContracts // generated types: contract objects, provider protocols, structs

App code never imports NitroModules. The C++ seam is hidden inside the Axion.xcframework binary; the BridgeKit and AxionContracts modules expose a pure-Swift interface. See Installation for how to add the xcframework to your project.

Implement the generated protocol, then register a factory:

import AxionContracts
final class MyAxionHostImpl: AxionHost {
func getCountryAndLanguage() throws -> GetCountryAndLanguageResult {
GetCountryAndLanguageResult(country: "DE", language: "de")
}
func getLiteral(_ params: GetLiteralParams) throws -> String {
NSLocalizedString(params.key, comment: "")
}
func getAppVersion() throws -> String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
}
func trackEvent(_ params: TrackEventParams) {
Analytics.shared.track(name: params.eventName)
}
func openUrl(_ params: OpenUrlParams) {
UIApplication.shared.open(URL(string: params.url)!)
}
}
// In AppDelegate or App.swift, before the React Native bridge starts:
let binding = BridgeKitRuntime.default.provide(AxionHostContract()) { MyAxionHostImpl() }
  • The factory is lazy — invoked on first resolution.
  • Pass scope: .global (default), .feature(name:), or .instance(feature:tag:) to scope the binding.
  • binding.close() de-registers the provider and signals Unprovided to any active consumers.
  • There is no ServiceLoader on iOS. Providers must be registered explicitly — typically in AppDelegate.application(_:didFinishLaunchingWithOptions:) or the @main App body — before the React Native bridge initializes.

consume returns a typed client proxy. Call methods directly:

import BridgeKit
import AxionContracts
let client: any AxionHostClient = BridgeKitRuntime.default.consume(AxionHostContract())
// async query:
let version = try await client.getAppVersion()
// fire (no result, errors swallowed):
client.trackEvent(TrackEventParams(eventName: "app_started"))
// Non-blocking check:
let ready = BridgeKitRuntime.default.isProvided(AxionHostContract())
// Async wait with timeout (throws BridgeKitError.CONTRACT_NOT_PROVIDED on timeout):
try await BridgeKitRuntime.default.awaitProvided(AxionHostContract(), timeoutMs: 5_000)

Consumed state arrives as an AsyncStream<BridgeValue<T>>. Iterate it on a background Task and dispatch to the main actor when updating UI:

Task {
for await bv in client.connectivity {
switch bv {
case .available(let v): updateUI(v) // provider live, value fresh
case .initial(let v): updateUI(v) // seeded initial, no provider yet
case .replacing(let last): showStale(last) // epoch swap in progress (250ms grace)
case .unprovided(let last): showOffline(last) // binding closed
}
}
}

BridgeValue<T> has four cases:

CaseMeaning
.available(T)Provider live, value is current.
.initial(T)Seeded from descriptor initial; no active provider yet.
.replacing(T?)Epoch swap in progress; last-known value held for up to 250ms.
.unprovided(T?)Binding closed; last-known value held if available.
// Global (default) — lives for the lifetime of the runtime:
BridgeKitRuntime.default.provide(MyContract()) { MyImpl() }
// Feature — tied to a named feature surface:
BridgeKitRuntime.default.provide(MyContract(), scope: .feature(name: "connect")) { MyImpl() }
// Instance — tied to a specific tagged instance of a feature:
BridgeKitRuntime.default.provide(
MyContract(),
scope: .instance(feature: "checkout", tag: sessionId)
) { MyImpl() }

Close the binding when the scope ends (e.g. on viewDidDisappear or onDisappear):

let binding = BridgeKitRuntime.default.provide(MyContract(), scope: .feature(name: "checkout")) {
CheckoutImpl()
}
// later:
binding.close()

iOS has no ServiceLoader equivalent. Every provider must be registered before the React Native bridge calls connectDispatcher. The recommended location is AppDelegate.application(_:didFinishLaunchingWithOptions:) for global-scope providers:

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Register global providers before React Native bridge starts:
_ = BridgeKitRuntime.default.provide(AxionHostContract()) { AxionHostImpl() }
// then initialize the bridge...
return true
}

For feature- and instance-scoped providers, register when the feature surface becomes active and close the binding when it disappears. See Auto-discovery (Android) for the Android ServiceLoader equivalent.