Mobile Development 24 min read

How We Turned Kotlin/Native on iOS into a First-Class Development Experience

This article details a deep engineering effort that transforms Kotlin Multiplatform on iOS into a first‑class development environment, covering the current pain points, root causes, a redesigned Bazel‑based build pipeline, parallel compilation techniques, performance gains, and future roadmap for mobile developers.

Bilibili Tech
Bilibili Tech
Bilibili Tech
How We Turned Kotlin/Native on iOS into a First-Class Development Experience

Introduction

This article is the fourth in the KMP Technology and Practice series, focusing on making Kotlin/Native a first‑class citizen on iOS, matching or surpassing the Apple‑provided ObjC/Swift workflow.

Why iOS Development Feels Unpleasant

According to the KMP 2025 roadmap, the top priority is to improve the “joy of coding” on iOS because the current feedback loop “edit‑one‑line → see result” is too long.

We define a pleasant experience as the shortest possible inner loop from code change to visible result.

Three Basic Demands

Coding : low‑latency input, smart completion, immediate error reporting; seamless navigation between ObjC/Swift/C/Kotlin.

Build System : incremental first, remote cache, distributed compilation, reproducible builds.

Debugging : complete symbols, stable breakpoints, consistent paths; fast and reliable attach/hot‑restart.

Current State of the KMP Ecosystem

Coding : Xcode’s support for ObjC/Swift is limited; Kotlin‑Native in IDEA works well, but most developers still use Xcode, leading to a fragmented experience.

Build System : Kotlin Gradle Plugin (KGP) is Gradle‑based, but Gradle is rarely used in iOS, and KGP’s incremental compilation, stability and reproducibility are far from the “never clean build” goal.

Debugging : Xcode’s Instruments are excellent, but KGP relies on the third‑party xcode‑kotlin bridge, causing source‑map and symbol issues.

Root Causes

The ecosystem tries to adapt to iOS through many parallel, incompatible paths (CocoaPods, SPM, Tuist, CMake, native .xcodeproj). Lack of a unified, declarative, reproducible standard leads to divergent dependency resolution, caching, and debugging behaviours.

Our Goals

Coding : achieve the same read/write experience for Kotlin‑Native in Xcode as Swift/ObjC, and in IDEA/VSCode as native Kotlin.

Build System : replace the Xcode‑centric workflow with Bazel, enable high‑concurrency incremental builds, and improve Kotlin‑Native’s parallel compilation capability.

Debugging : provide stable debugging in Xcode and VSCode comparable to native Swift/ObjC.

Decomposing Kotlin‑Native Issues

We identified six critical issues (marked 😈) such as lack of module‑level interop, missing source‑map in .klib, absent private implementation_deps, redundant IR checks, low concurrency in IR→Object aggregation, and naming‑clash problems.

Our Optimizations

We redesigned the architecture: split the original .klib into ori.klib, abi.klib, final.klib, final_cinterop.klib, and generated Clang modules via Bazel’s headerInfo. We also cut implementation_deps propagation and compiled each Kotlin module to a static .a file for maximal parallelism.

Parallel Compilation

Each kt_library is compiled independently with konanc -p static_cache, producing a .a file. The final artifact compiler is reduced to a simple LLVM linker, and remote caching is enabled, achieving “never clean build”.

Performance Results

Clean iOS build time reduced from 1959 s to 1865 s (≈5 %).

Final artifact compilation dropped from 590.7 s to 12.6 s (≈98 % reduction).

Incremental builds on small changes go from minutes to seconds; large changes see up to 40× speed‑up.

Summary

By deeply engineering the Kotlin/Native pipeline with Bazel, we turned KMP on iOS into a first‑class development experience, delivering faster builds, true module‑level interop, reliable debugging, and a feedback loop comparable to native Swift/ObjC.

Future Outlook

Short‑term: unify IDE workflow (VSCode + Kotlin LSP, IDEA + sourcekit‑lsp) and adopt Swift Direct Export. Mid‑term: pursue hot‑reload and richer diagnostics. Long‑term: give back to the community and influence the upstream Kotlin/Native roadmap.

Recommendations

Prefer pure‑Kotlin solutions when possible to minimise cross‑language glue.

For large monorepos, a custom Bazel pipeline remains essential.

Share findings with JetBrains and the open‑source community.

KlibInfo = provider(
  fields = {
    "klibs": "The output klibs",
    "transitive_compile_klibs": "The transitive compile klibs",
    "transitive_compile_interface_klibs": "The transitive compile interface klibs",
    "transitive_klibs": "The transitive klibs (All transitive klibs)",
    "transitive_interface_klibs": "The dependencies for compile klibs (attr: deps + implementation_deps)"
  }
)
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

iOSbuild optimizationKotlin MultiplatformBazelparallel compilation
Bilibili Tech
Written by

Bilibili Tech

Provides introductions and tutorials on Bilibili-related technologies.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.