How do you configure the iOS framework output from a KMP module using the framework DSL inside binaries.framework {} - name, static vs dynamic, baseName, and exporting dependencies?
answer
- binaries.framework { } per iOS target
- baseName = Swift import name
- isStatic = true -> static framework, no embed&sign
- export() re-exports API; needs api(...) dependency
- XCFramework() bundles device + simulator
basics
~20 sEach iOS target has a binaries.framework {} block where you set the framework name, choose static or dynamic linking, and export other KMP libraries so their public API also shows up in the Swift-facing framework.
solid answer
~40 sInside each Kotlin/Native iOS target you call binaries.framework { }. Key knobs: baseName sets the framework/module name Swift imports; isStatic = true produces a statically-linked framework (avoids embed-and-sign); without it you get a dynamic framework. export(project(":someModule")) makes a dependency's public API visible in the generated Objective-C header so Swift can call it - by default only the current module's API is exposed, transitive APIs are hidden. Exported deps must be declared as api(...) in the source set. transitiveExport = true re-exports an exported dep's own exports. For multiple architectures you typically build an XCFramework via the XCFramework() helper so devices and simulators ship in one bundle.
code
kotlin · 16 linesimport org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
kotlin {
val xcf = XCFramework("Shared")
listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared"
isStatic = true
export(project(":analytics")) // analytics must be api(...) in iosMain
xcf.add(this)
}
}
sourceSets {
iosMain.dependencies { api(project(":analytics")) }
}
}go deeper
Knows there's a framework block with a name but is fuzzy on static/dynamic and export.
Configures baseName, isStatic, and export(), and knows exported deps need api(...).
Explains header generation, Obj-C interop mapping, and when dynamic vs static is required; builds an XCFramework.
Designs the public Swift API surface deliberately (@ObjCName, @HiddenFromObjC, exported module boundaries) and the distribution artifact strategy.
## The framework DSL Kotlin/Native targets expose a `binaries` container. For Apple targets you add a framework binary: ```kotlin kotlin { iosArm64 { binaries.framework { baseName = "Shared" // Swift: import Shared isStatic = true // static vs dynamic linking export(project(":analytics")) // re-export another module's API } } iosSimulatorArm64 { binaries.framework { baseName = "Shared"; isStatic = true } } } ``` ## What each knob does - **baseName** - the framework name and the Swift module name. `import Shared` in Swift maps to `baseName = "Shared"`. Defaults to the Gradle project name. - **isStatic** - `true` produces a **static framework** (object code linked into the app binary at app link time). `false` yields a **dynamic framework** embedded and signed at app build time. Static avoids the 'embed & sign' step; dynamic can be required when multiple frameworks must share one Kotlin runtime. - **export(...)** - by default the generated Objective-C header only exposes the **current module's** public declarations; transitive library APIs are hidden. `export()` re-exports a dependency's public API into the header. The exported dependency must be declared with `api(...)` (not `implementation(...)`) in the corresponding source set, otherwise the build fails. - **transitiveExport = true** - re-exports an exported dependency's own exported deps as well. - **linkerOpts / freeCompilerArgs** - pass linker and compiler flags. ## Headers and interop The framework ships an **Objective-C/Swift-compatible header**. Kotlin types are mapped: `suspend` functions become completion-handler or async Swift methods, sealed classes/enums map to Obj-C classes. You can tune naming with `@ObjCName` and hide declarations with `@HiddenFromObjC`. ## XCFramework for shipping A single `.framework` is per-architecture. To ship device + simulator in one artifact, use the `XCFramework` helper: ```kotlin val xcf = XCFramework("Shared") listOf(iosArm64(), iosSimulatorArm64()).forEach { it.binaries.framework { baseName = "Shared"; xcf.add(this) } } ``` Running the `assembleSharedXCFramework` task produces an XCFramework bundle.
- Why must an exported dependency be declared with api(...) rather than implementation(...)?export() re-publishes the dependency's public API; api() keeps that API on the module's compile/publish surface, while implementation() hides it, so export() of an implementation dependency fails.
- When would you choose isStatic = false (dynamic)?When several frameworks in the app must share a single Kotlin/Native runtime, or tooling requires a dynamically embedded framework; otherwise static is simpler and avoids embed-and-sign.
saying these in an interview costs you the question
- Thinks export() works on an implementation(...) dependency
- Believes baseName is cosmetic and unrelated to the Swift import
- Confuses static vs dynamic framework consequences
- Doesn't know transitive APIs are hidden by default in the Obj-C header