<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Siddhant Panhalkar</title>
    <description>The latest articles on DEV Community by Siddhant Panhalkar (@siddhantpanhalkar).</description>
    <link>https://dev.to/siddhantpanhalkar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4093958%2F2dbc05ba-5b1a-4edc-a2d7-3629a728b76a.jpg</url>
      <title>DEV Community: Siddhant Panhalkar</title>
      <link>https://dev.to/siddhantpanhalkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siddhantpanhalkar"/>
    <language>en</language>
    <item>
      <title>I inspected my KMP iOS export header 61% of it was dead weight. Here’s what I found and built</title>
      <dc:creator>Siddhant Panhalkar</dc:creator>
      <pubDate>Tue, 25 Aug 2026 21:27:46 +0000</pubDate>
      <link>https://dev.to/siddhantpanhalkar/i-inspected-my-kmp-ios-export-header-61-of-it-was-dead-weight-heres-what-i-found-and-built-3mef</link>
      <guid>https://dev.to/siddhantpanhalkar/i-inspected-my-kmp-ios-export-header-61-of-it-was-dead-weight-heres-what-i-found-and-built-3mef</guid>
      <description>&lt;p&gt;If you are building an iOS app with Kotlin Multiplatform (KMP) or Compose Multiplatform, you might have opened your generated &lt;code&gt;Shared.h&lt;/code&gt; header at some point and wondered why it is 20,000+ lines long.&lt;/p&gt;

&lt;p&gt;I ran into this recently while optimizing one of my personal KMP apps. I kept seeing Objective-C classes generated for every single theme color, dimension constant, and internal state model, even though my Swift code never touched any of them.&lt;/p&gt;

&lt;p&gt;To get a clear picture of what was actually going on, I built a small Gradle plugin called &lt;strong&gt;&lt;a href="https://github.com/SiddhantPanhalkar/kmprofiler" rel="noopener noreferrer"&gt;kmprofiler&lt;/a&gt;&lt;/strong&gt;. It parses the generated Objective-C header, scans your Swift source files, and highlights which exported declarations have zero call sites in Swift.&lt;/p&gt;

&lt;p&gt;The numbers on my app caught me off guard, but cleaning it up took just a few minutes.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why does Kotlin/Native export so much?
&lt;/h3&gt;

&lt;p&gt;In Kotlin, declarations are &lt;code&gt;public&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;When targeting iOS, the Kotlin/Native compiler looks at every public class, top-level function, and property in your shared module and creates an Objective-C class interface and runtime method trampolines in the framework binary.&lt;/p&gt;

&lt;p&gt;The compiler cannot dead-strip these automatically because Objective-C relies on dynamic dispatch. It has to assume Swift or Objective-C could call them at runtime.&lt;/p&gt;

&lt;p&gt;If your UI is built with Compose Multiplatform or your Swift app only interacts with a couple of high-level bridge interfaces, most of those exported Objective-C wrappers end up being dead weight.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Audit: 459 Exports, 282 Unused
&lt;/h3&gt;

&lt;p&gt;When I ran &lt;code&gt;kmprofiler&lt;/code&gt; on my app (Framed), it gave me this breakdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### 📊 KMP iOS Export Profile&lt;/span&gt;

Export surface: 459 Kotlin declarations exported to Objective-C.
No direct Swift call site found for 282 of them (61.4% uncalled).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unused exports mostly fell into three buckets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;File Facades (&lt;code&gt;*Kt&lt;/code&gt; classes):&lt;/strong&gt; Top-level properties in files like &lt;code&gt;Dimens.kt&lt;/code&gt; (38 spacing constants) or &lt;code&gt;Color.kt&lt;/code&gt; generated synthetic Objective-C classes like &lt;code&gt;DimensKt&lt;/code&gt; with static getters for every single constant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal UI State and Callbacks:&lt;/strong&gt; Internal data classes like &lt;code&gt;HomeScreenCallbacks&lt;/code&gt; (23 callbacks) and state models were left &lt;code&gt;public&lt;/code&gt;, creating full Objective-C class descriptors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaked Library Types:&lt;/strong&gt; Third-party classes from Compose and Ktor leaked into the bridging header because of exposed function signatures.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Cleaning It Up
&lt;/h3&gt;

&lt;p&gt;I went through 5 files in my shared module and adjusted their visibility:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Making theme files internal:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dimens.kt / Color.kt / Type.kt&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.framed.app.ui.theme&lt;/span&gt;

&lt;span class="c1"&gt;// Before: val spacing16 = 16.dp&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;spacing16&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;beige&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFF1E5D7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Making UI models internal:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: data class HomeScreenCallbacks(...)&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;HomeScreenCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onRefresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(If you have classes that need to stay public for Android modules, you can use &lt;code&gt;@OptIn(ExperimentalObjCRefinement::class) @HiddenFromObjC&lt;/code&gt; to hide them only from iOS).&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The Numbers
&lt;/h3&gt;

&lt;p&gt;After making those 5 files &lt;code&gt;internal&lt;/code&gt;, I recompiled the release framework (&lt;code&gt;./gradlew linkReleaseFrameworkIosArm64&lt;/code&gt;) and checked the results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After (5 files fixed)&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Shared.h&lt;/code&gt; Header Lines&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;23,622 lines&lt;/td&gt;
&lt;td&gt;13,327 lines&lt;/td&gt;
&lt;td&gt;-10,295 lines (-43.6%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Header File Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.30 MB&lt;/td&gt;
&lt;td&gt;0.71 MB&lt;/td&gt;
&lt;td&gt;-587.5 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Shared.framework.o&lt;/code&gt; (Mach-O Object)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80.1 MB&lt;/td&gt;
&lt;td&gt;74.4 MB&lt;/td&gt;
&lt;td&gt;-5.70 MB saved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Framework Archive&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;249.6 MB&lt;/td&gt;
&lt;td&gt;243.9 MB&lt;/td&gt;
&lt;td&gt;-5.72 MB saved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uncalled Exports&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;282 declarations&lt;/td&gt;
&lt;td&gt;163 declarations&lt;/td&gt;
&lt;td&gt;119 dead exports removed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cleaning up those 5 files removed over 10,000 lines from the header and shaved 5.7 MB off the compiled object binary.&lt;/p&gt;




&lt;h3&gt;
  
  
  Trying It on Your Own Project
&lt;/h3&gt;

&lt;p&gt;I published &lt;strong&gt;&lt;code&gt;kmprofiler&lt;/code&gt; (v0.1.0)&lt;/strong&gt; to the Gradle Plugin Portal if you want to inspect your own KMP project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Apply the plugin in your shared module's &lt;code&gt;build.gradle.kts&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"io.github.siddhantpanhalkar.kmprofiler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;kmprofiler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;headerFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buildDirectory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bin/iosArm64/releaseFramework/Shared.framework/Headers/Shared.h"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;swiftSourceDirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projectDirectory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../iosApp"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;isStatic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Run the task:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew analyzeKmprofiler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints a summary to your terminal and saves a report to &lt;code&gt;build/reports/kmprofiler-report.md&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;Right now I'm working on v0.2.0 to parse Xcode Link Maps (&lt;code&gt;-Xlinker -map&lt;/code&gt;), which will calculate the exact linked byte attribution per Kotlin library in your final iOS binary. After that, I plan to add baseline diffing for CI so you can catch export bloat in pull requests.&lt;/p&gt;

&lt;p&gt;Repo is here: &lt;strong&gt;&lt;a href="https://github.com/SiddhantPanhalkar/kmprofiler" rel="noopener noreferrer"&gt;https://github.com/SiddhantPanhalkar/kmprofiler&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Feel free to try it out and let me know if you run into any issues or have suggestions!&lt;/p&gt;

</description>
      <category>kmp</category>
      <category>kotlin</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
