<?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: Hitarth Bhatt</title>
    <description>The latest articles on DEV Community by Hitarth Bhatt (@hitarth).</description>
    <link>https://dev.to/hitarth</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%2F4204764%2F68043851-4dc4-4159-8bce-31f78499201a.jpg</url>
      <title>DEV Community: Hitarth Bhatt</title>
      <link>https://dev.to/hitarth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hitarth"/>
    <language>en</language>
    <item>
      <title>My iOS App Got 22% Smaller With One Xcode Setting</title>
      <dc:creator>Hitarth Bhatt</dc:creator>
      <pubDate>Sat, 24 Oct 2026 22:30:39 +0000</pubDate>
      <link>https://dev.to/hitarth/my-ios-app-got-22-smaller-with-one-xcode-setting-3fn4</link>
      <guid>https://dev.to/hitarth/my-ios-app-got-22-smaller-with-one-xcode-setting-3fn4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Modularizing made our app bigger and slower to launch. One build setting won it all back, with zero code changes. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We didn't write a single new line of code. We just &lt;strong&gt;moved&lt;/strong&gt; some of it into frameworks, like every modularization guide tells you to.&lt;/p&gt;

&lt;p&gt;Then we archived the release build, and the app had gotten &lt;strong&gt;bigger&lt;/strong&gt;.&lt;/p&gt;

&lt;center&gt;&lt;img src="https://i.giphy.com/media/WRQBXSCnEFJIuxktnw/giphy.gif" alt="Confused math lady" width="504" height="322"&gt;&lt;/center&gt;

&lt;p&gt;This happened on a team I worked with, and it took us a week to crack. Here's the case file. I rebuilt the crime scene as a demo app, so every number below is real and reproducible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🗂️ &lt;strong&gt;CASE FILE: The App That Gained Weight&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Victim:&lt;/strong&gt; &lt;code&gt;MyApp&lt;/code&gt;, freshly modularized&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symptom:&lt;/strong&gt; got bigger after we only &lt;em&gt;moved&lt;/em&gt; code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suspects:&lt;/strong&gt; &lt;del&gt;Xcode&lt;/del&gt; · &lt;del&gt;the CI cache&lt;/del&gt; · &lt;del&gt;Mercury in retrograde&lt;/del&gt; · &lt;code&gt;public&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; one build setting, &lt;strong&gt;22% smaller&lt;/strong&gt;, &lt;strong&gt;0&lt;/strong&gt; frameworks loaded at launch&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔍 Exhibit A: 3 KB of Code in a 73 KB Box
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHitarthbhatt%2Fmergeable-libraries-demo%2Fmain%2Fdocs%2Fimages%2Fmodule-graph-v2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHitarthbhatt%2Fmergeable-libraries-demo%2Fmain%2Fdocs%2Fimages%2Fmodule-graph-v2.png" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All three modules are plain dynamic frameworks, which is what Xcode gives you by default. Now look at &lt;code&gt;SupportFeature&lt;/code&gt;: &lt;strong&gt;about 3 KB of code shipped as a 73 KB binary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a USB cable shipped in a microwave box.&lt;/p&gt;

&lt;p&gt;Every dynamic framework is split into four segments, and on arm64 each segment is padded to a 16 KB page, even when it's nearly empty. So each framework pays about &lt;strong&gt;64 KB of packaging&lt;/strong&gt; before it holds any code. On top of that, &lt;code&gt;dyld&lt;/code&gt; has to find, map, and bind every framework separately each time the app launches.&lt;/p&gt;

&lt;p&gt;Using CocoaPods with &lt;code&gt;use_frameworks!&lt;/code&gt;, or dynamic SPM products? Then every dependency is one more passport check at airport security on every cold launch. That's why "I added a few pods and my launch got slower" is such a popular Stack Overflow question.&lt;/p&gt;

&lt;center&gt;&lt;img src="https://i.giphy.com/media/QMHoU66sBXqqLqYvGO/giphy.gif" alt="This is fine" width="480" height="270"&gt;&lt;/center&gt;

&lt;p&gt;Padding explains the tiny frameworks. It doesn't explain why &lt;code&gt;CoreKit&lt;/code&gt; is 316 KB when it holds only 88 KB of code.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧤 Exhibit B: The Fingerprints
&lt;/h2&gt;

&lt;p&gt;When you move code out of the app target, the compiler greets you with this error, about four hundred times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'EventName' is inaccessible due to 'internal' protection level
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you do what everyone does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EventName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;appOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"App Open"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;center&gt;&lt;img src="https://i.giphy.com/media/qiFb2m5kSfyQrcXfFj/giphy.gif" alt="Scooby-Doo unmasking" width="480" height="270"&gt;&lt;/center&gt;

&lt;p&gt;Here's the interrogation transcript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🧑‍💻 &lt;strong&gt;Me:&lt;/strong&gt; Nobody even calls &lt;code&gt;appOpen&lt;/code&gt;. Why didn't you strip it?&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Linker:&lt;/strong&gt; It's &lt;code&gt;public&lt;/code&gt;. Someone outside the framework &lt;em&gt;might&lt;/em&gt; call it.&lt;/p&gt;

&lt;p&gt;🧑‍💻 &lt;strong&gt;Me:&lt;/strong&gt; Who?&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Linker:&lt;/strong&gt; …someone. I also kept its type metadata and added it to the export table. You're welcome.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every &lt;code&gt;public&lt;/code&gt; symbol costs you in three places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code the linker can't dead-strip&lt;/strong&gt; (&lt;code&gt;__TEXT&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type metadata&lt;/strong&gt; (&lt;code&gt;__const&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An entry in the export table&lt;/strong&gt; (&lt;code&gt;__LINKEDIT&lt;/code&gt;), which is the framework's phone book&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To prove it, I built the same &lt;code&gt;CoreKit&lt;/code&gt; (360 constants and 40 models) twice. The only change was the access level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CoreKit&lt;/th&gt;
&lt;th&gt;public&lt;/th&gt;
&lt;th&gt;internal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Binary&lt;/td&gt;
&lt;td&gt;316 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;221 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;__LINKEDIT&lt;/code&gt; (phone book)&lt;/td&gt;
&lt;td&gt;112 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exported symbols&lt;/td&gt;
&lt;td&gt;3,518&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same code, &lt;strong&gt;30% smaller&lt;/strong&gt;. The phone book was bigger than the town.&lt;/p&gt;




&lt;h2&gt;
  
  
  🪤 Exhibit C: The False Leads
&lt;/h2&gt;

&lt;p&gt;We spent a week refactoring our way out of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;EventName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                      &lt;span class="c1"&gt;// a struct&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;EventName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                        &lt;span class="c1"&gt;// no, an enum&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;EventName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                &lt;span class="c1"&gt;// enum cases!&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Constants&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// nest it&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;ConstantsEventName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;               &lt;span class="c1"&gt;// flatten it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The size report after each one:&lt;/p&gt;

&lt;center&gt;&lt;img src="https://i.giphy.com/media/pV0lVLeA0JXjBiO5Cp/giphy.gif" alt="They're the same picture" width="480" height="480"&gt;&lt;/center&gt;

&lt;p&gt;The shape of the type doesn't matter. The code is still &lt;code&gt;public&lt;/code&gt;, and the framework is still paying its packaging tax. The real fix is fewer &lt;code&gt;public&lt;/code&gt; declarations, but that's a quarter of cleanup work. We wanted a quicker win, so we asked a different question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the frameworks didn't have to stay separate in the build we ship?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 The Breakthrough: Why Not Both?
&lt;/h2&gt;

&lt;p&gt;Dynamic frameworks build fast but launch slow. Static libraries launch fast but build slow. Xcode 15's &lt;strong&gt;mergeable libraries&lt;/strong&gt; looked at that trade-off and said:&lt;/p&gt;

&lt;center&gt;&lt;img src="https://i.giphy.com/media/3o85xIO33l7RlmLR4I/giphy.gif" alt="Why not both" width="350" height="263"&gt;&lt;/center&gt;

&lt;p&gt;Here's the whole idea in one picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHitarthbhatt%2Fmergeable-libraries-demo%2Fmain%2Fdocs%2Fimages%2Fdebug-vs-release-v2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHitarthbhatt%2Fmergeable-libraries-demo%2Fmain%2Fdocs%2Fimages%2Fdebug-vs-release-v2.png" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Debug:&lt;/strong&gt; nothing changes. The app re-exports each framework, so incremental builds stay fast.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Release:&lt;/strong&gt; the linker merges every framework into the app binary and de-duplicates strings, selectors, and stubs along the way. The framework folders stay in the bundle for resources, but their binaries become empty shells.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 The Fix: One Setting
&lt;/h2&gt;

&lt;p&gt;Select your &lt;strong&gt;app target&lt;/strong&gt; → &lt;strong&gt;Build Settings&lt;/strong&gt; → search for &lt;strong&gt;Create Merged Binary&lt;/strong&gt; → set it to &lt;strong&gt;Automatic&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MERGED_BINARY_TYPE = automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No code changes, no new tools. &lt;code&gt;automatic&lt;/code&gt; merges every direct framework dependency built in your project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;One gotcha:&lt;/strong&gt; only &lt;em&gt;direct&lt;/em&gt; dependencies get merged. If &lt;code&gt;CoreKit&lt;/code&gt; reaches your app only through &lt;code&gt;SupportFeature&lt;/code&gt;, link it to the app target explicitly.&lt;/p&gt;

&lt;p&gt;🎛️ &lt;strong&gt;Want precise control?&lt;/strong&gt; Set &lt;code&gt;MERGED_BINARY_TYPE = manual&lt;/code&gt; on the app and &lt;code&gt;MERGEABLE_LIBRARY = YES&lt;/code&gt; on each framework you want merged.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚖️ The Verdict
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MyApp.app (Release)&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;560 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;436 KB (−22%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App executable&lt;/td&gt;
&lt;td&gt;73 KB&lt;/td&gt;
&lt;td&gt;305 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CoreKit&lt;/code&gt; executable&lt;/td&gt;
&lt;td&gt;316 KB&lt;/td&gt;
&lt;td&gt;33 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frameworks &lt;code&gt;dyld&lt;/code&gt; loads&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exported symbols&lt;/td&gt;
&lt;td&gt;3,518&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The app executable grew because all the code moved into it. The framework binaries left behind are empty shells.&lt;/p&gt;

&lt;p&gt;That took one build setting and zero code changes. The demo has three tiny frameworks. Real apps have dozens.&lt;/p&gt;




&lt;h2&gt;
  
  
  📜 The Fine Print
&lt;/h2&gt;

&lt;center&gt;&lt;img src="https://i.giphy.com/media/RN6OnheJ9ZEm4/giphy.gif" alt="With great power comes great responsibility" width="500" height="198"&gt;&lt;/center&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debug ≠ Release.&lt;/strong&gt; Merging happens only in Release builds, so test the build you actually ship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archives take longer.&lt;/strong&gt; Merging is extra linker work, so measure the impact on big apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual mode is easy to forget.&lt;/strong&gt; Put &lt;code&gt;MERGEABLE_LIBRARY = YES&lt;/code&gt; in a shared &lt;code&gt;.xcconfig&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Bundle(for:)&lt;/code&gt; on merged frameworks needs iOS 12 or later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dlopen&lt;/code&gt; paths need to point at the merged binary.&lt;/li&gt;
&lt;li&gt;Crash reports list the app as the image.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Worth it if&lt;/strong&gt; you have more than a handful of first-party frameworks and someone watches app size or launch time. ❌ &lt;strong&gt;Skip it if&lt;/strong&gt; you have one or two frameworks, or you ship binary SDKs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✅ Case Closed
&lt;/h2&gt;

&lt;p&gt;Your homework:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;nm -gU YourFramework | wc -l&lt;/code&gt;, and brace yourself.&lt;/li&gt;
&lt;li&gt;Change one thing that doesn't need to be &lt;code&gt;public&lt;/code&gt; back to &lt;code&gt;internal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;MERGED_BINARY_TYPE = automatic&lt;/code&gt; on a branch and compare the archives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/Hitarthbhatt/mergeable-libraries-demo" rel="noopener noreferrer"&gt;&lt;strong&gt;Grab the demo repo on GitHub&lt;/strong&gt;&lt;/a&gt;: the measuring scripts behind every number in this post.&lt;/p&gt;

&lt;p&gt;📺 &lt;strong&gt;Linker deep dive:&lt;/strong&gt; &lt;a href="https://developer.apple.com/videos/play/wwdc2023/10268/" rel="noopener noreferrer"&gt;Meet mergeable libraries (WWDC23)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I blamed Xcode, the CI cache, and the planets before I found the six-letter keyword I'd been typing on autopilot for years. Now I run &lt;code&gt;nm&lt;/code&gt; before I blame anything.&lt;/p&gt;

&lt;center&gt;&lt;img src="https://i.giphy.com/media/l0K42u9hCg0x6XbLa/giphy.gif" alt="Mic drop" width="480" height="262"&gt;&lt;/center&gt;

&lt;p&gt;&lt;em&gt;Send this to the teammate who just created their fifth framework this sprint.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>ai</category>
    </item>
    <item>
      <title>Tired of Slow Xcode Builds? Meet Bazel</title>
      <dc:creator>Hitarth Bhatt</dc:creator>
      <pubDate>Sat, 24 Oct 2026 13:35:03 +0000</pubDate>
      <link>https://dev.to/hitarth/tired-of-slow-xcode-builds-meet-bazel-397g</link>
      <guid>https://dev.to/hitarth/tired-of-slow-xcode-builds-meet-bazel-397g</guid>
      <description>&lt;h2&gt;
  
  
  Why Bazel? (The Pain First)
&lt;/h2&gt;

&lt;p&gt;Picture this: you change one line in a utility file. You hit &lt;code&gt;Cmd+B&lt;/code&gt;. Xcode decides to recompile half your project. Your MacBook fan spins up like it's about to take off. You go make coffee. You come back. It's still building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDYyNWM4NHJpMjNrNGFsenExazViNmkzMzJyemczNG9hbjhrcDFhMyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/IPWXYMP4t2ODvzwOYk/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDYyNWM4NHJpMjNrNGFsenExazViNmkzMzJyemczNG9hbjhrcDFhMyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/IPWXYMP4t2ODvzwOYk/giphy.gif" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xcode's build system is fine for small apps. But as projects grow — more files, more targets, more developers — it starts to crack. There's no real caching, builds aren't reproducible across machines, and CI pipelines become a 20-minute waiting game.&lt;/p&gt;

&lt;p&gt;This is the problem Bazel was built to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Bazel?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bazel&lt;/strong&gt; is an open-source build system originally developed at Google (internally called Blaze). It's the same system used to build Google Search, Gmail, and Android. Companies like Uber, Airbnb, and Spotify use it for their iOS monorepos.&lt;/p&gt;

&lt;p&gt;The key ideas behind Bazel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hermetic builds&lt;/strong&gt; — Bazel controls every input to a build. Same code + same machine = identical output, every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intelligent caching&lt;/strong&gt; — Bazel hashes your inputs. If nothing changed, nothing rebuilds. This works locally &lt;em&gt;and&lt;/em&gt; across your whole team on CI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Incremental by design&lt;/strong&gt; — Only the targets affected by your change get rebuilt. Not half the project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Language agnostic&lt;/strong&gt; — One build system for Swift, Kotlin, C++, Go, Python. Useful when you're scaling to a monorepo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Xcode compatible&lt;/strong&gt; — Thanks to &lt;code&gt;rules_xcodeproj&lt;/code&gt;, you still get a full &lt;code&gt;.xcodeproj&lt;/code&gt; to work with. You don't have to give up your IDE.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bazel has a reputation for being complex. That reputation isn't wrong — but the basics are simpler than you'd think. Let's build something.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;By the end of this post, you'll have a working iOS SwiftUI app built entirely with Bazel. You'll be able to run it in Xcode, build it from the terminal, and understand every file in the project.&lt;/p&gt;

&lt;p&gt;Here's the final project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MyApp/
├── MODULE.bazel          # Dependencies (like Package.swift)
├── BUILD.bazel           # Build rules (your targets)
├── .bazelrc              # Bazel configuration flags
├── Sources/
│   └── BazelApp.swift    # Your SwiftUI app
└── Resources/
    ├── BUILD.bazel        # Exports Info.plist to Bazel
    └── Info.plist         # Standard iOS app info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;Podfile&lt;/code&gt;. No &lt;code&gt;Package.swift&lt;/code&gt;. No &lt;code&gt;Cartfile&lt;/code&gt;. Just five files and a SwiftUI view. Let's go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before we start, make sure you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Bazel via Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;bazel

&lt;span class="c"&gt;# Verify&lt;/span&gt;
bazel &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need Xcode installed with a valid simulator. That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The App We're Building
&lt;/h2&gt;

&lt;p&gt;The SwiftUI code is intentionally simple — the point is the &lt;em&gt;build system&lt;/em&gt;, not the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sources/BazelApp.swift&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;BazelApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&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;A standard SwiftUI entry point. Nothing special here — and that's the point. Bazel doesn't care what your Swift code looks like. It just needs to know how to build it.&lt;/p&gt;




&lt;h2&gt;
  
  
  MODULE.bazel — Your Dependency Manager
&lt;/h2&gt;

&lt;p&gt;Create a file called &lt;code&gt;MODULE.bazel&lt;/code&gt; at your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;bazel_dep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules_apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4.1.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build_bazel_rules_apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;bazel_dep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules_swift&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build_bazel_rules_swift&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;bazel_dep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules_xcodeproj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.10.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of &lt;code&gt;MODULE.bazel&lt;/code&gt; as your &lt;code&gt;Package.swift&lt;/code&gt; — except it manages the build system's dependencies, not your app's. It tells Bazel which external rule sets to download.&lt;/p&gt;

&lt;p&gt;Here's what each line does:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rules_apple&lt;/code&gt; — Provides iOS-specific build rules like &lt;code&gt;ios_application&lt;/code&gt;. Without this, Bazel doesn't know how to build an &lt;code&gt;.ipa&lt;/code&gt; or run an app on a simulator.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rules_swift&lt;/code&gt; — Provides &lt;code&gt;swift_library&lt;/code&gt; and &lt;code&gt;swift_binary&lt;/code&gt;. Bazel's core doesn't know Swift exists — this rule set teaches it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rules_xcodeproj&lt;/code&gt; — The magic rule that generates a &lt;code&gt;.xcodeproj&lt;/code&gt; from your Bazel targets. This is how you keep using Xcode while Bazel handles the actual building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Think of MODULE.bazel as your Package.swift — except it doesn't pick a fight with Xcode every time you open the project.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Enable Bzlmod
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.bazelrc&lt;/code&gt; at your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;common --enable_bzlmod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. That's it.&lt;/p&gt;

&lt;p&gt;This flag tells Bazel to use &lt;strong&gt;Bzlmod&lt;/strong&gt; — its modern, module-based dependency system. &lt;code&gt;MODULE.bazel&lt;/code&gt; only works when Bzlmod is enabled. Without this flag, Bazel falls back to the older &lt;code&gt;WORKSPACE&lt;/code&gt; approach and ignores your &lt;code&gt;MODULE.bazel&lt;/code&gt; entirely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You've seen&lt;/em&gt; &lt;code&gt;Podfile&lt;/code&gt; &lt;em&gt;longer than this just to install Alamofire.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  BUILD.bazel — The Heart of Bazel
&lt;/h2&gt;

&lt;p&gt;This is where things get interesting. Create &lt;code&gt;BUILD.bazel&lt;/code&gt; at the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@build_bazel_rules_apple//apple:ios.bzl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ios_application&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@build_bazel_rules_swift//swift:swift.bzl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;swift_library&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@rules_xcodeproj//xcodeproj:defs.bzl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xcodeproj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_level_target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;swift_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sources/**/*.swift&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;//visibility:public&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;ios_application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NewApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bundle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.yourname.ios&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;families&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iphone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;infoplists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;//Resources:Info.plist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;minimum_os_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;17.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:Source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;//visibility:public&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;xcodeproj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xcodeproj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;project_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top_level_targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;top_level_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:NewApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_environments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;simulator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&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;Let's break this down piece by piece.&lt;/p&gt;

&lt;h3&gt;
  
  
  load() — Importing Rules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@build_bazel_rules_apple//apple:ios.bzl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ios_application&lt;/span&gt;&lt;span class="sh"&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;code&gt;load()&lt;/code&gt; is how you import rules into a BUILD file — like &lt;code&gt;import&lt;/code&gt; in Swift. Each rule you use must be explicitly loaded from its source.&lt;/p&gt;

&lt;h3&gt;
  
  
  swift_library
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;swift_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;srcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sources/**/*.swift&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;//visibility:public&lt;/span&gt;&lt;span class="sh"&gt;"&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;This compiles all your Swift files into a library named &lt;code&gt;Source&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;glob(["Sources/**/*.swift"])&lt;/code&gt; — finds all &lt;code&gt;.swift&lt;/code&gt; files recursively. Same as &lt;code&gt;**/*.swift&lt;/code&gt; in your terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;visibility = ["//visibility:public"]&lt;/code&gt; — allows other targets (like your app) to depend on this library.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ios_application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;ios_application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NewApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bundle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.yourname.ios&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;families&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iphone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;infoplists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;//Resources:Info.plist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;minimum_os_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;17.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:Source&lt;/span&gt;&lt;span class="sh"&gt;"&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;This is your app target — equivalent to your Xcode application target.&lt;/p&gt;

&lt;p&gt;The key attribute is &lt;code&gt;deps = [":Source"]&lt;/code&gt;. The &lt;code&gt;:&lt;/code&gt; prefix means &lt;em&gt;"a target defined in this same BUILD file."&lt;/em&gt; This is how Bazel knows to compile &lt;code&gt;Source&lt;/code&gt; before building &lt;code&gt;NewApp&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  xcodeproj
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;xcodeproj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xcodeproj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;project_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top_level_targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;top_level_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:NewApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_environments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;simulator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&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;This generates your &lt;code&gt;.xcodeproj&lt;/code&gt; file so Xcode stays happy. Best of both worlds.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Your entire app configuration is 25 lines. Your&lt;/em&gt; &lt;code&gt;project.pbxproj&lt;/code&gt; &lt;em&gt;has entered the chat... and it's crying.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How Bazel Sees Your Project
&lt;/h3&gt;

&lt;p&gt;Bazel models your build as a &lt;strong&gt;dependency graph&lt;/strong&gt; — each target is a node, and &lt;code&gt;deps&lt;/code&gt; are edges. Here's what your project looks like:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;graph TD
    A[xcodeproj] --&amp;gt; B[NewApp :ios_application]
    B --&amp;gt; C[Source :swift_library]
    B --&amp;gt; D[Info.plist]
    C --&amp;gt; E[BazelApp.swift]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This graph is why Bazel is fast. When you change &lt;code&gt;BazelApp.swift&lt;/code&gt;, Bazel walks the graph and only rebuilds &lt;code&gt;Source&lt;/code&gt; and &lt;code&gt;NewApp&lt;/code&gt;. Everything else is untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources/BUILD.bazel
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;Resources/BUILD.bazel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;exports_files&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Info.plist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short and sweet. This makes &lt;code&gt;Info.plist&lt;/code&gt; visible to the root &lt;code&gt;BUILD.bazel&lt;/code&gt; via &lt;code&gt;//Resources:Info.plist&lt;/code&gt;. Without this, Bazel's sandbox can't see files outside the current package.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Bazel Caching Works
&lt;/h2&gt;

&lt;p&gt;Before you run your first build, it's worth understanding &lt;em&gt;why&lt;/em&gt; Bazel is faster than Xcode for repeated builds.&lt;/p&gt;

&lt;p&gt;Xcode's incremental build system uses file timestamps. It asks: &lt;em&gt;"Has this file been modified since last build?"&lt;/em&gt; It works, but it's imprecise — a &lt;code&gt;touch&lt;/code&gt; on a file triggers a rebuild even if nothing changed.&lt;/p&gt;

&lt;p&gt;Bazel uses &lt;strong&gt;content hashing&lt;/strong&gt;. It asks: &lt;em&gt;"Have the actual contents of every input changed?"&lt;/em&gt; Here's the full flow:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[You make a code change] --&amp;gt; B[Bazel hashes all inputs\nfor affected targets]
    B --&amp;gt; C{Cache hit?}
    C --&amp;gt;|Yes — nothing changed| D[Skip rebuild entirely\nUse cached output]
    C --&amp;gt;|No — inputs changed| E[Rebuild the target]
    E --&amp;gt; F[Store new output in cache]
    F --&amp;gt; G[Build complete]
    D --&amp;gt; G&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The cache works &lt;strong&gt;locally&lt;/strong&gt; (your machine) and can be extended to a &lt;strong&gt;remote cache&lt;/strong&gt; shared across your team and CI. When your teammate builds the same commit you already built, they get your cached outputs. Zero rebuild.&lt;/p&gt;

&lt;p&gt;This is the real superpower — not just fast local builds, but a shared build cache that makes CI fast too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running Your First Build
&lt;/h2&gt;

&lt;p&gt;You have all the files. Let's build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Generate the Xcode project&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;bazel run //:xcodeproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs the &lt;code&gt;xcodeproj&lt;/code&gt; target, which generates &lt;code&gt;MyApp.xcodeproj&lt;/code&gt;. The &lt;code&gt;//&lt;/code&gt; means "from the project root", &lt;code&gt;:xcodeproj&lt;/code&gt; is the target name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Build the app&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;bazel build //:NewApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run will be slow — Bazel downloads dependencies and builds from scratch. Subsequent builds will be dramatically faster thanks to caching.&lt;/p&gt;

&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO: Analyzed target //:NewApp
INFO: Found 1 target...
Target //:NewApp up-to-date:
  bazel-bin/NewApp.ipa
INFO: Build completed successfully
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Open in Xcode&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;open MyApp.xcodeproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select a simulator, hit &lt;code&gt;Cmd+R&lt;/code&gt;. Your app runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Bazel Target Syntax
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;//&lt;/code&gt; and &lt;code&gt;:&lt;/code&gt; syntax trips up every beginner. Here's the rule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;//&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;//:NewApp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target &lt;code&gt;NewApp&lt;/code&gt; in the root &lt;code&gt;BUILD.bazel&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;//Resources:Info.plist&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File &lt;code&gt;Info.plist&lt;/code&gt; in the &lt;code&gt;Resources&lt;/code&gt; package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:Source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target &lt;code&gt;Source&lt;/code&gt; in the &lt;em&gt;current&lt;/em&gt; BUILD file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;When it builds successfully on the first try, you will feel like an absolute wizard. When it doesn't — welcome to the club. We have snacks and Stack Overflow tabs.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Should YOU Adopt Bazel?
&lt;/h2&gt;

&lt;p&gt;Honest answer: it depends. Here's a clear breakdown:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Bazel if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your team has &lt;strong&gt;10+ iOS developers&lt;/strong&gt; and builds are slowing everyone down&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your &lt;strong&gt;CI pipeline takes more than 10 minutes&lt;/strong&gt; and remote caching would help&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're moving toward a &lt;strong&gt;monorepo&lt;/strong&gt; with shared code across platforms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need &lt;strong&gt;reproducible builds&lt;/strong&gt; — same output on every machine, every time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're planning to scale and want the build system to scale with you&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stick with Xcode if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You're a &lt;strong&gt;solo developer&lt;/strong&gt; on a relatively small app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your team has &lt;strong&gt;no bandwidth&lt;/strong&gt; for the initial setup investment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're &lt;strong&gt;under deadline pressure&lt;/strong&gt; — Bazel has a real learning curve&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your project is &lt;strong&gt;simple enough&lt;/strong&gt; that Xcode's build system handles it fine&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Bazel is like a standing desk — amazing long-term investment, great for your health, but the first week you'll wonder why you did this to yourself. Stick with it.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMzZuNnJtOXA4bzdjbjFkZDI1cjd2NmVsdGp2Y3ltMnlvbnE1NWo5ZSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/BzCLJGxXQbwH09jzq0/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMzZuNnJtOXA4bzdjbjFkZDI1cjd2NmVsdGp2Y3ltMnlvbnE1NWo5ZSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/BzCLJGxXQbwH09jzq0/giphy.gif" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sweet spot for Bazel is mid-to-large teams with growing codebases. For solo developers and small teams, the overhead may not be worth it yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;You just built an iOS app with Bazel from scratch. Here's where to go from here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add third-party dependencies&lt;/strong&gt; — Integrate Swift packages via &lt;code&gt;rules_swift_package_manager&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-module setup&lt;/strong&gt; — Split your app into multiple &lt;code&gt;swift_library&lt;/code&gt; targets for faster incremental builds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Remote caching&lt;/strong&gt; — Set up a shared cache with Buildbuddy or Google Cloud Storage for blazing fast CI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explore the Bazel ecosystem&lt;/strong&gt; — &lt;code&gt;rules_apple&lt;/code&gt; supports watchOS, tvOS, and macOS targets too&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full project from this post is on GitHub — clone it, explore the files, and use it as your starting point:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Hitarthbhatt/bazel-ios-starter" rel="noopener noreferrer"&gt;&lt;strong&gt;github.com/Hitarthbhatt/bazel-ios-starter&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built this to understand Bazel properly. The first build failed five times. The sixth worked, and I immediately understood why teams adopt this. If I can get here, you can too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? Share it with an iOS dev who's tired of slow builds. Have questions? Drop them in the comments — I'd love to help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>ai</category>
      <category>programming</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
