Skip to content
Skip
3k

SkipBridge

Bidirectional bridging between Swift and Kotlin/Java for Skip Fuse apps.

Note: Most developers will never use SkipBridge directly. It is an internal infrastructure package that powers Skip’s transparent Swift/Kotlin interoperability in Fuse mode. When you enable bridging: true in a module’s skip.yml, the Skip build plugin automatically generates the JNI glue code that calls into SkipBridge — no manual integration is required.

For a complete reference of which Swift language features and types can be bridged, see the Bridging Reference documentation.

SkipBridge enables compiled Swift code (running via the Swift Android SDK) and Kotlin/Java code (running on the JVM or Android runtime) to call each other across the JNI boundary. It handles:

  • Object lifecycle management — Swift objects held by Kotlin are reference-counted via opaque Int64 pointers (SwiftObjectPointer), preventing premature deallocation while avoiding retain cycles.
  • Type conversion — Automatic marshalling between Swift and Kotlin/Java types (e.g., ArrayList, DictionaryMap, Datejava.util.Date, URLjava.net.URI, UUIDjava.util.UUID).
  • Closure bridging — Swift closures map to Kotlin FunctionN lambda objects (up to 5 parameters), with support for async/suspend variants.
  • Inheritance — Bridged class hierarchies are preserved across the boundary (up to 4 levels of inheritance).
  • Async/await — Swift async functions and properties bridge to Kotlin suspend functions, and AsyncStream/AsyncThrowingStream bridge to kotlinx.coroutines.flow.Flow.
  • Dynamic accessAnyDynamicObject provides @dynamicMemberLookup-based access to arbitrary Kotlin/Java objects from Swift, backed by a Kotlin Reflector class that handles property access and method dispatch with overload resolution.

The core Swift types live in the SkipBridge module:

TypePurpose
SwiftObjectPointerInt64 alias representing an opaque pointer to a Swift object, managed with Unmanaged retain/release semantics
BridgedToKotlinProtocol marking Swift types that are projected to Kotlin
BridgedFromKotlinProtocol marking Kotlin projections back to Swift
BridgedTypesEnum cataloging all bridgeable types (primitives, collections, dates, URLs, etc.) with conversion logic
AnyDynamicObject@dynamicMemberLookup wrapper for calling into arbitrary Kotlin/Java objects via reflection
JavaBackedClosure<R>Wraps a Kotlin lambda (JObject) so it can be invoked from Swift, with variants for 0–5 parameters and suspend support
SwiftEquatable / SwiftHashableWrappers that project Swift Equatable/Hashable conformances to Kotlin’s equals/hashCode

The skip.bridge.Reflector class provides the Kotlin-side reflection engine for AnyDynamicObject. It supports:

  • Constructing Kotlin objects by class name with arguments
  • Reading and writing properties by name with type conversion
  • Invoking methods with overload resolution scored by argument compatibility

System.swift handles loading the compiled Swift .so libraries into the JVM/Android runtime across multiple build scenarios — Xcode-launched tests with an embedded JVM, SwiftPM-launched tests, Gradle test execution via Robolectric, and on-device Android execution via System.loadLibrary.

SkipBridge itself is configured as a native-mode module (mode: 'native' in its skip.yml). Its Gradle build configuration orchestrates the Android NDK toolchain to compile Swift source into .so shared libraries that are packaged into the app’s JNI library directory.

Modules that depend on SkipBridge typically declare bridging in their own skip.yml:

skip:
mode: 'native'
bridging: true

An optional kotlincompat bridging mode translates Swift collection and standard library types to their Kotlin equivalents (e.g., Arraykotlin.collections.List) for more natural consumption from pure Kotlin code:

skip:
mode: 'native'
bridging:
enabled: true
options: 'kotlincompat'
  • swift-jni — JNI C headers and Swift JNI wrapper
  • SkipLib — Swift standard library extensions for Skip