<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://paraipan9.github.io/swift-performance/feed.xml" rel="self" type="application/atom+xml" /><link href="https://paraipan9.github.io/swift-performance/" rel="alternate" type="text/html" /><updated>2026-06-17T12:27:30+00:00</updated><id>https://paraipan9.github.io/swift-performance/feed.xml</id><title type="html">Swift Performance</title><subtitle>In-depth articles on Swift performance — ARC, value types, concurrency, and compiler optimizations.</subtitle><author><name>Paraipan</name></author><entry><title type="html">Understanding App Launch</title><link href="https://paraipan9.github.io/swift-performance/2026/06/17/understanding-app-launch/" rel="alternate" type="text/html" title="Understanding App Launch" /><published>2026-06-17T00:00:00+00:00</published><updated>2026-06-17T00:00:00+00:00</updated><id>https://paraipan9.github.io/swift-performance/2026/06/17/understanding-app-launch</id><content type="html" xml:base="https://paraipan9.github.io/swift-performance/2026/06/17/understanding-app-launch/"><![CDATA[<p>App launch is a user experience interruption, as Apple puts it. It is the moment our user is waiting on us, before they can do anything else.</p>

<h2 id="why-it-matters">Why it matters</h2>

<p>Our app’s launch is our user’s first experience with our app, and as such it should be delightful. It is also important to test on older devices, to make sure the experience holds up across a broad set of users with different device capabilities. A launch that feels fast on the latest hardware can feel sluggish on a device that is a few years old.</p>

<h2 id="launch-types">Launch types</h2>

<p>There are three kinds of launch, and they differ by how much of our app is already resident in memory.</p>

<ul>
  <li><strong>Cold:</strong>
    <ul>
      <li>Happens after a reboot.</li>
      <li>The app is not in memory.</li>
      <li>No process exists. Basically, the app was not launched in a very long time.</li>
    </ul>
  </li>
  <li><strong>Warm:</strong> after a cold launch has happened, every kill and relaunch of the app is a warm launch.
    <ul>
      <li>The app was recently terminated.</li>
      <li>The app is partially in memory.</li>
      <li>No process exists.</li>
    </ul>
  </li>
  <li><strong>Resume:</strong> this is when the app is in the background and the user re-enters it from the home screen or the app switcher.
    <ul>
      <li>The app is suspended.</li>
      <li>The app is fully in memory.</li>
      <li>The process exists.</li>
    </ul>
  </li>
</ul>

<h2 id="the-400ms-target">The 400ms target</h2>

<p>Apple says that to achieve a responsive cold launch, we should aim for roughly 400ms to render the first frame. That way we have pixels displayed to the user during the launch animation, and by the time that launch animation completes, the app is interactive and responsive.</p>

<p>I believe that is a very optimistic case, and it depends on the size of the app and other factors. But of course we can still find ways to reduce app launch time and aim for the lowest possible value.</p>

<p>To reduce app launch time, it is important to understand what happens during launch.</p>

<p>Launch generally starts when the user taps our icon on the home screen. Then, over the next 100 or so milliseconds, iOS does the necessary system-side work to initialize our app. That leaves us developers about 300 milliseconds to create our views, load our content, and generate the first frame.</p>

<h2 id="the-phases-of-an-app-launch">The phases of an app launch</h2>

<p>The launch flows through these phases:</p>

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:1.5em 0;font-size:0.9em;font-weight:600;line-height:1.6;">
  <span style="background:#5856d6;color:#fff;padding:6px 12px;border-radius:8px;white-space:nowrap;">System Interface</span>
  <span style="color:#6e6e73;">→</span>
  <span style="background:#0071e3;color:#fff;padding:6px 12px;border-radius:8px;white-space:nowrap;">Runtime Init</span>
  <span style="color:#6e6e73;">→</span>
  <span style="background:#34c759;color:#fff;padding:6px 12px;border-radius:8px;white-space:nowrap;">UIKit Init</span>
  <span style="color:#6e6e73;">→</span>
  <span style="background:#ff9500;color:#fff;padding:6px 12px;border-radius:8px;white-space:nowrap;">Application Init</span>
  <span style="color:#6e6e73;">→</span>
  <span style="background:#ff2d55;color:#fff;padding:6px 12px;border-radius:8px;white-space:nowrap;">Initial Frame Render</span>
  <span style="color:#6e6e73;">→</span>
  <span style="background:#8e8e93;color:#fff;padding:6px 12px;border-radius:8px;white-space:nowrap;">Extended</span>
</div>

<h3 id="1-system-interface">1. System Interface</h3>

<p>DYLD, the dynamic linker, loads our shared libraries and frameworks. For an in-depth analysis, watch this presentation: <a href="https://www.youtube.com/watch?v=p-dvAOHlLEc">A tale of dyld, and how iOS launches your app</a>.</p>

<p>This is a section we can hardly influence, because it is system work. But we can influence it by avoiding dynamic library loading so we can make use of dyld3, which caches runtime dependencies and warm launches, which improves app launch performance.</p>

<h3 id="2-runtime-init">2. Runtime Init</h3>

<p>This is when the system initializes the Objective-C and Swift runtimes.</p>

<p>We cannot do much here either, except avoid static initializations and frameworks that use static initialization. But if we must use static initialization, we should consider moving code out of class load, which is invoked every time during launch, into class initialize, which is lazily invoked the first time we use a method within our class.</p>

<h3 id="3-uikit-init">3. UIKit Init</h3>

<p>This is when the system instantiates our <code class="language-plaintext highlighter-rouge">UIApplication</code> and our <code class="language-plaintext highlighter-rouge">UIApplicationDelegate</code>. For the most part, this is system-side work: setting up event processing and integration with the system. But we can still affect this phase if we subclass <code class="language-plaintext highlighter-rouge">UIApplication</code> or do any work in <code class="language-plaintext highlighter-rouge">UIApplicationDelegate</code> initializers.</p>

<h3 id="4-application-init">4. Application Init</h3>

<p>This is where we can have the most impact on our app launch time. Nowadays everyone is using <code class="language-plaintext highlighter-rouge">UIScene</code>s, so it is important to create our view controllers only in <code class="language-plaintext highlighter-rouge">scene(_:willConnectTo:options:)</code> and not also in <code class="language-plaintext highlighter-rouge">application(_:didFinishLaunchingWithOptions:)</code>. Doing both leads to a common pitfall that results in performance losses and, likely, unpredictable bugs in our code base.</p>

<h3 id="5-frame-render">5. Frame Render</h3>

<p>This one is relatively straightforward. This is when we create our views, perform layout, and then draw them: <code class="language-plaintext highlighter-rouge">loadView</code>, <code class="language-plaintext highlighter-rouge">viewDidLoad</code>, <code class="language-plaintext highlighter-rouge">layoutSubviews</code>.</p>

<p>We can affect this phase by reducing the number of views in our hierarchy. We can do that by flattening our views to use fewer of them, or by lazily loading views that are not shown during launch. We should also take a look at our Auto Layout and see if we can reduce the number of constraints we are using.</p>

<h3 id="6-extended">6. Extended</h3>

<p>This is the app-specific period from our first commit until we show our final frame to the user. This is when we load the asynchronous data. Not every app has this phase. During it, the app should be interactive and responsive.</p>

<h2 id="how-do-we-know-we-need-improvements">How do we know we need improvements?</h2>

<p>How do we know we need to make our app launch faster? We of course need to measure first the app launch under certain conditions and in a certain states. I will leave that topic for another article, because I wanted to keep this one short and concise.</p>

<p>Thanks for reading. If you found it helpful, you can share it with others.</p>

<h2 id="useful-link">Useful link</h2>

<ul>
  <li><a href="https://developer.apple.com/videos/play/wwdc2019/423/">WWDC19 - Optimizing App Launch</a></li>
</ul>]]></content><author><name>Paraipan</name></author><category term="App Launch" /><category term="Performance" /><category term="UIKit" /><summary type="html"><![CDATA[App launch is our user's first experience with our app. Here's what actually happens during launch, the phases involved, and where we can influence them.]]></summary></entry><entry><title type="html">Swizzling: Debugging the First Responder Chain</title><link href="https://paraipan9.github.io/swift-performance/2026/04/03/swizzling-debugging-first-responder/" rel="alternate" type="text/html" title="Swizzling: Debugging the First Responder Chain" /><published>2026-04-03T00:00:00+00:00</published><updated>2026-04-03T00:00:00+00:00</updated><id>https://paraipan9.github.io/swift-performance/2026/04/03/swizzling-debugging-first-responder</id><content type="html" xml:base="https://paraipan9.github.io/swift-performance/2026/04/03/swizzling-debugging-first-responder/"><![CDATA[<p>Sometimes a bug lives entirely in behaviour you cannot set a breakpoint on. The first responder chain is one of those places. UIKit moves focus across responders silently: no delegate, no notification by default, no easy way to know who called <code class="language-plaintext highlighter-rouge">becomeFirstResponder</code> or when.</p>

<p>This is a case where method swizzling is the right tool.</p>

<h2 id="what-swizzling-is">What swizzling is</h2>

<p>Swizzling is a runtime technique from the Objective-C world. Every Objective-C method is just an entry in a method table, a mapping from selector to function pointer. The runtime exposes <code class="language-plaintext highlighter-rouge">method_exchangeImplementations</code>, which swaps two entries in that table.</p>

<p>After the swap, calling the original selector runs your replacement function. Calling the replacement selector runs the original function. This is the core trick that makes swizzling work.</p>

<p>In Swift, you can only swizzle methods that are visible to the Objective-C runtime. That means methods on <code class="language-plaintext highlighter-rouge">NSObject</code> subclasses, or methods annotated with <code class="language-plaintext highlighter-rouge">@objc</code>.</p>

<p><code class="language-plaintext highlighter-rouge">UIResponder</code> is an <code class="language-plaintext highlighter-rouge">NSObject</code> subclass, so every method on it, including <code class="language-plaintext highlighter-rouge">becomeFirstResponder</code> and <code class="language-plaintext highlighter-rouge">resignFirstResponder</code>, is swizzleable.</p>

<h2 id="the-strange-behaviour">The strange behaviour</h2>

<p>I was investigating some unexpected focus behaviour in an app that embeds a <code class="language-plaintext highlighter-rouge">WKWebView</code>. Things were not working as expected: focus was moving in a way that did not match what the code was doing, and I could not tell which part of the hierarchy was actually first responder at any given moment.</p>

<p>The tricky part with <code class="language-plaintext highlighter-rouge">WKWebView</code> is that it manages its own internal view hierarchy, including <code class="language-plaintext highlighter-rouge">WKContentView</code>, a private WebKit view that actually holds the first responder when the web content is focused. You cannot inspect this easily, and there is no delegate or notification that tells you when it takes or releases focus.</p>

<p>I needed to see the full sequence of responder transitions across the entire app, including those private views.</p>

<h2 id="the-swizzle">The swizzle</h2>

<p>I added an extension on <code class="language-plaintext highlighter-rouge">UIResponder</code>:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">UIResponder</span> <span class="p">{</span>

    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">swizzleBecomeFirstResponder</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">originalBecome</span> <span class="o">=</span> <span class="nf">class_getInstanceMethod</span><span class="p">(</span><span class="kt">UIResponder</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="kd">#selector(</span><span class="nf">becomeFirstResponder</span><span class="kd">)</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">swizzledBecome</span> <span class="o">=</span> <span class="nf">class_getInstanceMethod</span><span class="p">(</span><span class="kt">UIResponder</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="kd">#selector(</span><span class="nf">swizzled_becomeFirstResponder</span><span class="kd">)</span><span class="p">)</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">original</span> <span class="o">=</span> <span class="n">originalBecome</span><span class="p">,</span> <span class="k">let</span> <span class="nv">swizzled</span> <span class="o">=</span> <span class="n">swizzledBecome</span> <span class="p">{</span>
            <span class="nf">method_exchangeImplementations</span><span class="p">(</span><span class="n">original</span><span class="p">,</span> <span class="n">swizzled</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">swizzled_becomeFirstResponder</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"---&gt; becomeFirstResponder: </span><span class="se">\(</span><span class="nf">type</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
        <span class="k">return</span> <span class="nf">swizzled_becomeFirstResponder</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The key detail is the recursive-looking call inside <code class="language-plaintext highlighter-rouge">swizzled_becomeFirstResponder</code>. After <code class="language-plaintext highlighter-rouge">method_exchangeImplementations</code> runs, the selector <code class="language-plaintext highlighter-rouge">swizzled_becomeFirstResponder</code> points to the <em>original</em> <code class="language-plaintext highlighter-rouge">becomeFirstResponder</code> implementation. So calling <code class="language-plaintext highlighter-rouge">swizzled_becomeFirstResponder()</code> from within the replacement is not infinite recursion. It is the call to the original method. This is the standard swizzling pattern.</p>

<p>I activated it in <code class="language-plaintext highlighter-rouge">AppDelegate</code>, before the window is shown:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">application</span><span class="p">(</span><span class="n">_</span> <span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span><span class="p">,</span>
                 <span class="n">didFinishLaunchingWithOptions</span> <span class="nv">launchOptions</span><span class="p">:</span> <span class="p">[</span><span class="kt">UIApplication</span><span class="o">.</span><span class="kt">LaunchOptionsKey</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]?)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
    <span class="kt">UIResponder</span><span class="o">.</span><span class="nf">swizzleBecomeFirstResponder</span><span class="p">()</span>
    <span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I added an equivalent swizzle for <code class="language-plaintext highlighter-rouge">resignFirstResponder</code> to get the full picture.</p>

<h2 id="what-the-output-revealed">What the output revealed</h2>

<p>With the swizzle in place, the console printed every responder transition in order:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---&gt; becomeFirstResponder: RootNavigationController
---&gt; becomeFirstResponder: BrowserWindow
---&gt; becomeFirstResponder: LaunchScreenViewController
---&gt; resignFirstResponder: LocationView
---&gt; resignFirstResponder: LocationView
---&gt; becomeFirstResponder: BrowserViewController
---&gt; becomeFirstResponder: WKContentView
</code></pre></div></div>

<p>This immediately gave me a complete picture of the hierarchy and the sequence of events, something that would have been impossible to reconstruct from breakpoints alone.</p>

<p>One thing this technique is particularly useful for with <code class="language-plaintext highlighter-rouge">WKWebView</code> is confirming whether <code class="language-plaintext highlighter-rouge">WKContentView</code> is becoming first responder or not. <code class="language-plaintext highlighter-rouge">WKContentView</code> is a private class, so you cannot reference it directly in code or set a targeted breakpoint on it. But because the swizzle is on <code class="language-plaintext highlighter-rouge">UIResponder</code>, the base class for the entire responder chain, it catches everything, including private UIKit internals. If <code class="language-plaintext highlighter-rouge">WKContentView</code> takes focus, it shows up in the log like any other class.</p>

<p>Without the swizzle, none of this sequence was visible. The log turned an opaque focus problem into a readable timeline.</p>

<h2 id="when-to-reach-for-this">When to reach for this</h2>

<p>Swizzling is not a tool you leave in production code. It changes global behaviour for every instance of a class, in every part of the app, for the lifetime of the process. That is too broad for anything except a debugging session.</p>

<p>The right workflow:</p>

<ol>
  <li>Add the swizzle behind a <code class="language-plaintext highlighter-rouge">#if DEBUG</code> block, or simply in a debug branch</li>
  <li>Reproduce the bug and read the log</li>
  <li>Fix the root cause</li>
  <li>Remove the swizzle entirely before shipping</li>
</ol>

<p>There is also a correctness requirement: swizzling must happen exactly once per method. Calling <code class="language-plaintext highlighter-rouge">method_exchangeImplementations</code> twice on the same pair restores the original state, effectively a no-op. If you call it from multiple places or inside code that runs more than once, you will get unpredictable results. Using <code class="language-plaintext highlighter-rouge">AppDelegate.application(_:didFinishLaunchingWithOptions:)</code> is a safe call site because it runs exactly once.</p>

<h2 id="what-to-reach-for-first">What to reach for first</h2>

<p>Before swizzling, check the cheaper options:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">UIApplication.shared.keyWindow?.perform(#selector(UIResponder.resignFirstResponder))</code> to manually clear focus</li>
  <li>A recursive walk of the view hierarchy that checks <code class="language-plaintext highlighter-rouge">isFirstResponder</code></li>
  <li>Breakpoint on <code class="language-plaintext highlighter-rouge">becomeFirstResponder</code> in Xcode’s symbolic breakpoint list.</li>
</ul>

<p>Swizzling is the right choice when the problem spans multiple classes, involves a sequence of events, or is too fast to catch with a debugger. For a first responder bug that crosses view controllers and windows, it was the right call.</p>]]></content><author><name>Paraipan</name></author><category term="Debugging" /><category term="Runtime" /><category term="UIKit" /><summary type="html"><![CDATA[Method swizzling lets you intercept any Objective-C method at runtime. Here's how I used it to trace the full first responder chain, to understand a strange focus behaviour.]]></summary></entry><entry><title type="html">Copy-on-Write: It’s Not About Struct vs Class</title><link href="https://paraipan9.github.io/swift-performance/2026/03/23/copy-on-write-its-not-about-struct-vs-class/" rel="alternate" type="text/html" title="Copy-on-Write: It’s Not About Struct vs Class" /><published>2026-03-23T00:00:00+00:00</published><updated>2026-03-23T00:00:00+00:00</updated><id>https://paraipan9.github.io/swift-performance/2026/03/23/copy-on-write-its-not-about-struct-vs-class</id><content type="html" xml:base="https://paraipan9.github.io/swift-performance/2026/03/23/copy-on-write-its-not-about-struct-vs-class/"><![CDATA[<p>A common misconception in Swift performance discussions is that copy-on-write is a way to make structs behave like classes for performance. It isn’t. CoW is a <strong>semantics decision</strong>. You’re choosing when storage is duplicated, not choosing between value and reference semantics.</p>

<p>This article walks through a concrete implementation, explains what happens at the ARC level, and backs every claim with benchmark data.</p>

<h2 id="the-problem-with-heap-heavy-structs">The problem with heap-heavy structs</h2>

<p>Consider a struct with 10 fields, all heap-allocated:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">struct</span> <span class="kt">HeavyStruct</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>           <span class="c1">// heap buffer + ARC</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">identifier</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">category</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">tags</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>         <span class="c1">// heap array + ARC retain per element</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">keywords</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">authors</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">relatedIDs</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">scores</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>          <span class="c1">// heap array</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">metadata</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When you copy this struct, Swift retains every reference individually. That’s roughly <strong>10+ ARC retain calls per copy</strong>. One for each <code class="language-plaintext highlighter-rouge">String</code>, one for each <code class="language-plaintext highlighter-rouge">Array</code> buffer.</p>

<p>The retains are cheap individually, but they add up.</p>

<h2 id="what-cow-actually-does">What CoW actually does</h2>

<p>Copy-on-Write moves all fields into a single class instance (<code class="language-plaintext highlighter-rouge">_Storage</code>) and wraps it in the struct. Basically, it is a struct, backed by a class:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">struct</span> <span class="kt">HeavyCOWStruct</span> <span class="p">{</span>

    <span class="kd">final</span> <span class="kd">class</span> <span class="kt">Storage</span> <span class="p">{</span>
        <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span>
        <span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span>
        <span class="k">var</span> <span class="nv">identifier</span><span class="p">:</span> <span class="kt">String</span>
        <span class="k">var</span> <span class="nv">category</span><span class="p">:</span> <span class="kt">String</span>
        <span class="k">var</span> <span class="nv">tags</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
        <span class="k">var</span> <span class="nv">keywords</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
        <span class="k">var</span> <span class="nv">authors</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
        <span class="k">var</span> <span class="nv">relatedIDs</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span>
        <span class="k">var</span> <span class="nv">scores</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
        <span class="k">var</span> <span class="nv">metadata</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>

        <span class="kd">func</span> <span class="nf">copy</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Storage</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="k">var</span> <span class="nv">_storage</span><span class="p">:</span> <span class="kt">Storage</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now copying the struct is <strong>1 ARC retain</strong> on <code class="language-plaintext highlighter-rouge">_storage</code>, regardless of how many fields it contains. The actual buffer duplication is deferred to the first mutation and only if another owner of the same storage exists:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
    <span class="k">get</span> <span class="p">{</span> <span class="n">_storage</span><span class="o">.</span><span class="n">name</span> <span class="p">}</span>
    <span class="k">set</span> <span class="p">{</span>
        <span class="c1">// Only allocates if someone else holds a reference to _storage.</span>
        <span class="k">if</span> <span class="o">!</span><span class="nf">isKnownUniquelyReferenced</span><span class="p">(</span><span class="o">&amp;</span><span class="n">_storage</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">_storage</span> <span class="o">=</span> <span class="n">_storage</span><span class="o">.</span><span class="nf">copy</span><span class="p">()</span>
        <span class="p">}</span>
        <span class="n">_storage</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">newValue</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">isKnownUniquelyReferenced</code> is an O(1) check on the ARC reference count. If the count is 1, meaning no other variable holds this storage, the mutation happens in place with zero allocation.</p>

<h2 id="this-is-about-semantics-not-struct-vs-class">This is about semantics, not struct vs class</h2>

<p>The struct still has <strong>value semantics</strong>. Two variables holding the same <code class="language-plaintext highlighter-rouge">HeavyCOWStruct</code> are independent values. Mutating one does not affect the other:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="kt">HeavyCOWStruct</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"Swift"</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">b</span> <span class="o">=</span> <span class="n">a</span>                 <span class="c1">// b._storage === a._storage — shared, 1 ARC retain</span>
<span class="n">a</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">"Performance"</span>    <span class="c1">// new Storage only if b is still alive</span>
<span class="c1">// a.name == "Performance", b.name == "Swift"</span>
</code></pre></div></div>

<p>If you used a plain <code class="language-plaintext highlighter-rouge">class</code> instead, <code class="language-plaintext highlighter-rouge">b</code> would reflect the mutation. CoW gives you the copy safety of a struct with the copy efficiency of a class, but it is <strong>not free</strong>. Every mutating property setter pays the <code class="language-plaintext highlighter-rouge">isKnownUniquelyReferenced</code> check, and when storage is shared, it pays a full allocation.</p>

<h2 id="the-benchmark">The benchmark</h2>

<p>To measure the difference, I ran two tests, a copy followed by a mutation on both versions. Each test was run with <code class="language-plaintext highlighter-rouge">-c release</code> to get optimized builds.</p>

<p><strong>Results:</strong></p>

<table>
  <thead>
    <tr>
      <th>Version</th>
      <th>Time per operation</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">HeavyStruct</code> (plain)</td>
      <td>7.42 µs</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">HeavyCOWStruct</code> (CoW)</td>
      <td>5.40 µs</td>
    </tr>
  </tbody>
</table>

<p><strong>CoW was ~27% faster.</strong></p>

<p>The mutation makes the difference clear. The plain struct is fully copied at assignment (all 10 ARC retains), then mutated in place. The CoW struct pays only 1 ARC retain at assignment. The mutation checks <code class="language-plaintext highlighter-rouge">isKnownUniquelyReferenced</code> and because <code class="language-plaintext highlighter-rouge">original</code> is no longer in use at that point, the reference count is 1, so no <code class="language-plaintext highlighter-rouge">Storage.copy()</code> is needed. The mutation happens in place.</p>

<p>CoW wins both steps: cheaper copy, free mutation.</p>

<h2 id="when-cow-does-not-help">When CoW does not help</h2>

<p>CoW is not always the right choice. It adds overhead when:</p>

<p><strong>Mutations are frequent with shared owners.</strong> If two variables hold the same CoW struct and you mutate through one of them, <code class="language-plaintext highlighter-rouge">Storage.copy()</code> runs a full heap allocation of all fields. This is more expensive than a plain struct mutation.</p>

<p><strong>Fields are small scalars.</strong> A struct with <code class="language-plaintext highlighter-rouge">Bool</code>, <code class="language-plaintext highlighter-rouge">Int</code>, and <code class="language-plaintext highlighter-rouge">Float</code> fields has nothing to gain. There are no heap references to consolidate. The struct copies by value directly.</p>

<p><strong>Instances are always freshly constructed.</strong> CoW shares storage between copies of the <em>same instance</em>. If every use of the struct calls <code class="language-plaintext highlighter-rouge">init</code>, there is never a shared storage to benefit from.</p>

<p><strong>Property access is in a tight loop.</strong> Every read goes through <code class="language-plaintext highlighter-rouge">_storage.field</code> an extra pointer indirection compared to a plain struct. For a type accessed thousands of times per frame, this adds up.</p>

<h2 id="advice">Advice</h2>

<blockquote>
  <p>Use CoW when a struct has many reference-type fields, is frequently copied, and mutations either don’t happen or happen while the copy is the sole owner. Skip it for small structs, scalar-heavy types, or anything mutated in a tight loop with shared owners.</p>
</blockquote>

<p>The data, not the pattern should drive the decision.</p>

<h2 id="further-reading">Further reading</h2>

<ul>
  <li><a href="https://github.com/apple/swift-nio-examples/blob/main/http2-client/Sources/http2-client/Types.swift#L22">CoW in practice: swift-nio-examples</a>: A real-world example from Apple’s SwiftNIO team showing how CoW-backed value types are used in production networking code.</li>
  <li><a href="https://www.youtube.com/watch?v=iLDldae64xE">High-Performance Systems In Swift</a>: Apple engineer, Johannes Weiss from the SwiftNIO team, walks through the performance model for value types, reference types and the gradual steps to a high performant system. Essential context for every decision covered in this article.</li>
</ul>]]></content><author><name>Paraipan</name></author><category term="ARC" /><category term="Value/Reference Types" /><category term="Copy-on-Write" /><summary type="html"><![CDATA[Most Swift performance articles frame CoW as a struct optimization. It's not. It's a semantics decision and the benchmark data shows exactly when it pays off.]]></summary></entry></feed>