
Writing shared business logic once and shipping it to both Android and iOS turns out to be far less painful than I expected.
Background
The premise of Kotlin Multiplatform (KMP) has always sounded attractive: define your data models, network layer, database access, and business logic once in Kotlin, then consume it natively on both Android and iOS. No JavaScript bridge, no WebView, no shared runtime — just compiled native code on each platform.
The skeptic’s counterargument is usually “sure, but the integration is a mess.” For iOS especially, the assumption is that wiring a Kotlin library into an Xcode project will involve fighting Gradle, broken symlinks, and mysterious linker errors for a weekend before anything actually runs.
Working on KotlinGameBench — a Kotlin framework featuring game logic with a minimax AI engine — gave me a concrete reason to actually test this. The game logic (board state, move validation, search algorithm) is pure business logic with no platform dependencies. A perfect candidate for sharing.
How It Works
KMP compiles shared Kotlin code into an xcframework — a bundle containing compiled binaries for multiple Apple architectures (arm64 for device, x86_64/arm64 for simulator). This is the standard Apple format for distributing precompiled libraries and Xcode knows exactly how to consume it.
The Gradle configuration is straightforward. You declare your targets in build.gradle.kts:
kotlin {
iosArm64()
iosSimulatorArm64()
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.framework {
baseName = "GameBench"
}
}
}
Running ./gradlew assembleXCFramework produces a GameBench.xcframework directory. You drag it into Xcode under Frameworks, Libraries, and Embedded Content, set it to Embed & Sign, and you’re done. No CocoaPods required, no SPM manifest to maintain.
The Kotlin compiler generates Objective-C headers for all public declarations in your shared module. These headers are what Xcode actually sees — Swift consumes them via the Objective-C bridge, which is fully automatic.
Code
On the Kotlin side, a shared data class and interface look like this:
// Shared module — compiles to both Android and iOS
data class ConnectMove(val column: Int, val player: Player) : Move
interface SearchableBoard {
fun makeMove(move: Move)
fun undoMove(move: Move)
fun evaluate(): Int
fun isTerminal(): Boolean
fun availableMoves(player: Player): List<Move>
}
The generated Objective-C header exposes ConnectMove as an @interface and SearchableBoard as a @protocol. Swift sees them as native types:
import GameBench
// Swift usage — no wrappers needed
let board = ConnectBoard()
let engine = SearchEngine()
let bestMove = engine.getBestMove(board: board, player: .white, depth: 6)
The naming convention follows Objective-C style — so Kotlin’s getBestMove(board:player:depth:) arrives in Swift with labeled parameters intact. It reads naturally.
Trade-offs & Limitations
The main friction point is that KMP generates Objective-C headers, not Swift. This means some Kotlin constructs don’t map cleanly: sealed classes arrive as abstract base classes with subclasses rather than Swift enums, Kotlin’s suspend functions require a compatibility layer (kotlinx-coroutines-native with @Throws annotations), and generics are often erased to id. The SKIE plugin by Touchlab addresses most of these — it post-processes the Kotlin compiler output to generate proper Swift-friendly APIs including real async/await support and Swift enums from sealed classes. It’s worth adding from the start rather than retrofitting later.
My Take
The xcframework integration story is genuinely solid. The part that surprised me most is how syntactically close Kotlin and Swift actually are — data classes vs structs, when vs switch, extension functions, named parameters — switching context between the two is far less jarring than switching between, say, Kotlin and TypeScript. The Objective-C interop layer is the only real rough edge, and SKIE largely papers over it. If your iOS app has any meaningful business logic — validation rules, state machines, parsing, algorithms — KMP is worth the setup cost. The break-even point is lower than you’d think.
Tags: Kotlin, KotlinMultiplatform, iOS, Swift
