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.
Imports
Section titled “Imports”import BridgeKit // runtime: BridgeKitRuntime, Binding, BridgeValue, Scope, BridgeKitErrorimport AxionContracts // generated types: contract objects, provider protocols, structsApp 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.
Provide a contract
Section titled “Provide a contract”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 signalsUnprovidedto any active consumers.- There is no
ServiceLoaderon iOS. Providers must be registered explicitly — typically inAppDelegate.application(_:didFinishLaunchingWithOptions:)or the@mainApp body — before the React Native bridge initializes.
Consume a contract
Section titled “Consume a contract”consume returns a typed client proxy. Call methods directly:
import BridgeKitimport 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"))Readiness checks
Section titled “Readiness checks”// 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)State is BridgeValue<T>
Section titled “State is BridgeValue<T>”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:
| Case | Meaning |
|---|---|
.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. |
Scopes
Section titled “Scopes”// 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()Provider registration timing
Section titled “Provider registration timing”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.