<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Swiftwright]]></title><description><![CDATA[Subscribe for deep dives into Swift, SwiftUI, iOS internals, concurrency, performance, and Apple platform engineering.]]></description><link>https://swiftwright.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!cSw4!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59075462-3aa3-4185-ab25-4ae020813b48_1254x1254.png</url><title>Swiftwright</title><link>https://swiftwright.dev</link></image><generator>Substack</generator><lastBuildDate>Fri, 19 Jun 2026 07:47:15 GMT</lastBuildDate><atom:link href="https://swiftwright.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[John Baker Lent]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[jbakerlent@gmail.com]]></webMaster><itunes:owner><itunes:email><![CDATA[jbakerlent@gmail.com]]></itunes:email><itunes:name><![CDATA[John]]></itunes:name></itunes:owner><itunes:author><![CDATA[John]]></itunes:author><googleplay:owner><![CDATA[jbakerlent@gmail.com]]></googleplay:owner><googleplay:email><![CDATA[jbakerlent@gmail.com]]></googleplay:email><googleplay:author><![CDATA[John]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Body, Identity, and Lifetime in SwiftUI]]></title><description><![CDATA[An exploration of SwiftUI view updates]]></description><link>https://swiftwright.dev/p/body-identity-and-lifetime-in-swiftui</link><guid isPermaLink="false">https://swiftwright.dev/p/body-identity-and-lifetime-in-swiftui</guid><dc:creator><![CDATA[John]]></dc:creator><pubDate>Wed, 03 Jun 2026 23:13:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cSw4!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59075462-3aa3-4185-ab25-4ae020813b48_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In SwiftUI, the phrase &#8220;the view updated&#8221; is overloaded. View updates can involve many things &#8212; body recomputation, structural identity comparison, state lookup, and more. </p><p>We know that SwiftUI views are ephemeral value types that can be reconstructed frequently, but just because the <em>body</em> property is reevaluated does not mean that an entire new view with a different identity and new state is created. These two events are easily conflated since they may both involve the same SwiftUI code being executed again, but they have very different consequences. So let&#8217;s first understand how to distinguish simple changes to a view&#8217;s description from changes to its identity.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading jbakerlent! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>One caveat that I feel obligated to add is that SwiftUI's reconciliation rules aren't documented and have shifted across releases. So the description below reflects the current model, but not necessarily a contractual guarantee.</p><h3>View Descriptions</h3><p>A description change is a new answer to &#8220;what should this view look like now?&#8221; A description-level change occurs when SwiftUI reevaluates a view&#8217;s <em>body</em> and produces a new description of the interface. The view still occupies the same structural position in the view tree, but some aspect of its rendered output has changed.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;4ccb1525-0e94-428a-ad0f-c49e4ccd805e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")

            Button("Increment") {
                count += 1
            }
        }
    }
}</code></pre></div><p>In the above snippet, changes to <em>count</em> cause <em>body</em> to be recomputed, producing a new description of the interface. But SwiftUI can still reconcile that description against the same structural identity, so the state associated with the counter remains intact.</p><h3>View Identity</h3><p>An identity change is a new answer to &#8220;is this still the same conceptual view?&#8221; Identity is closely tied to structure. When the shape of the produced view tree changes &#8212; a conditional branch is taken, an element appears or disappears, a view moves between containers &#8212; SwiftUI must determine how elements in the new tree correspond to elements in the previous tree. Not every change qualifies &#8212; changing a modifier value usually leaves identity intact, for instance &#8212; but when correspondence genuinely breaks, new identities and new lifetimes are established.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:&quot;9fe38ce8-ef66-4713-af28-7b437ea21e2d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">if isHighlighted {
    Text(title)
        .font(.headline)
} else {
    Text(title)
        .font(.body)
}</code></pre></div><p>Here, the change is no longer purely descriptive. Each branch of the conditional has its own identity. When isHighlighted flips, SwiftUI tears down the Text from the true branch and creates the Text from the false branch &#8212; these are two distinct identities, not one view receiving a new description. That said, toggling back and forth doesn&#8217;t spawn endless new identities; it will alternate between two stable ones, each with its own lifetime.</p><p>This example is particularly surprising because both branches are just Text(title) with a different font, so it&#8217;s tempting to expect SwiftUI to recognize them as the same view and simply update the font. It doesn&#8217;t. The if/else is a structural fork, and structure determines identity. Because Text holds no state, the identity change here costs you nothing observable &#8212; but the font change won&#8217;t animate across the branch boundary, and if either branch held a stateful child view, that state would be discarded on every toggle. (You can collapse this into a single identity by changing it to <em>Text(title).font(isHighlighted ? .headline : .body)</em>.)</p><p>For a deeper look at structural identity and how it is represented, see <a href="https://jbakerlent.substack.com/p/how-result-builders-and-opaque-return">this article</a>. </p><h3>State Storage</h3><p>One of the reasons the distinction between identity and description matters is that ordinary let and var properties are stored directly on the View struct and are therefore recreated with the view value. @State is different. Although a @State property is declared inside the view, its storage is managed externally by SwiftUI and tied to the view&#8217;s identity in the view tree.</p><p>As a result, if some upstream event causes CounterView (in the above example) to be recreated, count is not necessarily reinitialized. As long as SwiftUI can reconcile the new description against the same identity, the existing state storage can be looked up and reused.</p><h3>Performance Implications</h3><p>This is why recomputing body is usually less consequential. SwiftUI is designed to repeatedly ask views for new descriptions of the interface. As long as those descriptions can be reconciled with the same identity, the lifetime associated with that identity continues, and SwiftUI can preserve the state attached to it.</p><p>Identity changes are more significant. When a view moves to a different position, is replaced by a different branch, or otherwise no longer corresponds to the same place in the view tree, SwiftUI may establish a new lifetime. At that point, any state associated with the old identity is not associated with the new view.</p><p>In summary, the body property is ephemeral and corresponds to the view&#8217;s description, while state corresponds to its identity and is continuous. All the long-lived pieces are stored elsewhere, which allows SwiftUI to cheaply recompute the body frequently.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading jbakerlent! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How Result Builders and Opaque Return Types Make SwiftUI Possible]]></title><description><![CDATA[The line var body: some View is likely the most-written line in SwiftUI.]]></description><link>https://swiftwright.dev/p/how-result-builders-and-opaque-return</link><guid isPermaLink="false">https://swiftwright.dev/p/how-result-builders-and-opaque-return</guid><dc:creator><![CDATA[John]]></dc:creator><pubDate>Sat, 16 May 2026 23:02:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cSw4!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59075462-3aa3-4185-ab25-4ae020813b48_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The line <em>var body: some View</em> is likely the most-written line in SwiftUI. We have a sense of the purpose it serves, but likely don&#8217;t stop to examine the Swift language features that make it possible. Examining the usage of <strong>result builders</strong> and <strong>opaque return types</strong> in detail helps illuminate SwiftUI's identity and diffing behavior, and gives a clearer understanding of those language features themselves.</p><h4>Structural Identity in SwiftUI</h4><p>Because views are values and not objects &#8212; views are descriptions of UI, not instances of it &#8212; SwiftUI needs to be able to efficiently compare them to determine when to update the view tree. Unlike frameworks such as React, which primarily reconcile UI structure at runtime using a virtual DOM, SwiftUI pushes much more structural information into the type system at compile time. Achieving this requires a mechanism capable of assigning concrete static types to deeply nested hierarchies containing different view types and control flow such as <code>if</code> statements.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading jbakerlent! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>SwiftUI heavily relies on structural identity &#8212; the position and static type information of views in the hierarchy &#8212; to determine how updates should be applied efficiently. This means that erasing the type to something like<code> </code><em>AnyView</em> removes structural type information SwiftUI could otherwise use during diffing and update propagation. It would also force SwiftUI to rely more heavily on dynamic dispatch.</p><h4>Encoding View Hierarchies with <code>@ViewBuilder</code></h4><p>SwiftUI&#8217;s solution for encoding the structure of a view is to use a result builder. The content closure of each SwiftUI view is implicitly annotated with one: @ViewBuilder. It transforms the body into a concrete nested generic type at compile time. Let&#8217;s look at a simple view definition as an example.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">struct ExampleView: View {
    var showImage: Bool

    var body: some View {
        HStack {
            Text("Hello")
            if showImage {
                Image(systemName: "star")
            } else {
                Text("World")
            }
            Button("Tap me") { }
        }
    }
}</code></pre></div><p>The compiler relies on @ViewBuilder to desugar it to look roughly like this.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">ViewBuilder.buildBlock(
    Text("Hello"),
    ViewBuilder.buildEither(
        first: Image(systemName: "star")
    ),
    Button("Tap me") { }
)</code></pre></div><p>The <em>else</em> branch would similarly use <em>buildEither(second:)</em>. </p><p>That yields this fully specified type.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;swift&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-swift">HStack&lt;TupleView&lt;(Text, _ConditionalContent&lt;Image, Text&gt;, Button&lt;Text&gt;)&gt;&gt;</code></pre></div><p>You can see how beautifully this fits into SwiftUI&#8217;s declarative style; the type itself is a compile-time map of the hierarchy &#8212; every branch of control flow and every container is encoded structurally.</p><h4>Hiding Complexity with Opaque Return Types</h4><p>This is only half of the solution, though. If every view were exposed as a highly nested generic type, every place a view was referenced would need to include its fully specified type and cascading updates would be needed for simple changes to a single view in a hierarchy. This would create a very brittle structure. Swift addresses this with another language feature: opaque return types. Opaque return types are sometimes described as the opposite of generics because instead of the caller choosing the type, the implementation chooses it. This lets the type be hidden to callers &#8212; hence the word &#8220;opaque&#8221;. The compiler knows the full concrete type, while callers only know that it conforms to the <code>View</code> protocol.</p><h4>The Takeaway</h4><p>SwiftUI&#8217;s design works because these features complement each other perfectly: <em>@ViewBuilder</em> preserves the complete structural shape of the hierarchy for the framework and compiler, while <em>some View</em> hides that complexity from API boundaries. The result is a system that remains declarative and ergonomic while still retaining compile-time knowledge about the UI tree. It&#8217;s impressive how much complexity this design shifts into the compiler and type system. Understanding that flow gives a much clearer picture of SwiftUI&#8217;s architecture and many of its behavioral nuances.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading jbakerlent! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Fork-Join Pattern in Swift Concurrency]]></title><description><![CDATA[Swift Concurrency gives us two APIs for running work in parallel: async let and TaskGroup. They look quite different at first, but they're both expressions of the same underlying pattern, and I find that understanding that pattern makes the relationship between them much clearer.]]></description><link>https://swiftwright.dev/p/the-fork-join-pattern-in-swift-concurrency</link><guid isPermaLink="false">https://swiftwright.dev/p/the-fork-join-pattern-in-swift-concurrency</guid><dc:creator><![CDATA[John]]></dc:creator><pubDate>Thu, 07 May 2026 20:47:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cSw4!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59075462-3aa3-4185-ab25-4ae020813b48_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Swift Concurrency gives us two APIs for running work in parallel: <em>async let</em> and <em>TaskGroup</em>. They look quite different at first, but they're both expressions of the same underlying pattern, and I find that understanding that pattern makes the relationship between them much clearer.</p><h3>Concurrency vs. parallelism</h3><p>First, a useful starting distinction around terms (from Rob Pike): <em>concurrency</em> is about structure, <em>parallelism</em> is about execution.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading jbakerlent! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><em>Concurrency</em> is a way of structuring a program so multiple logically independent operations can make progress independently &#8212; whether interleaved on one core or executing simultaneously on many.</p><p>On the other hand, <em>parallelism</em> is multiple operations physically executing at the same instant on separate hardware (multiple cores, GPUs). It&#8217;s a property of how the program runs.</p><h3>The trouble with callbacks and promises</h3><p>With those definitions in hand: the question for any concurrent language is <em>how</em> it lets you express that structure. Most languages give you callbacks or futures and promises. In their raw form, callbacks and promises tend to behave like a kind of cross-thread goto: control flow escapes lexical structure, and lifetimes become difficult to reason about locally. You may be familiar with goto's downfall with the advent of structured programming, thanks to some opinion pieces by Dijkstra (pronounced Deek-strah, by the way). This is, not coincidentally, where the "Structured" in Structured Concurrency comes from &#8212; it's the same idea of respecting lexical scope applied to a new domain and helps simplify a lot of the pain points of GCD.</p><p>If you're curious to explore this more, <a href="https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/">this article</a> is a great walkthrough, particularly the breakdown of how each of these are semantically equivalent to <em>go</em> statements:</p><blockquote><ul><li><p>Registering a callback is semantically equivalent to starting a background thread that (a) blocks until some event occurs, and then (b) runs the callback. (Though obviously the implementation is different.) So in terms of high-level control flow, registering a callback is essentially a go statement.</p></li><li><p>Futures and promises are the same too: when you call a function and it returns a promise, that means it&#8217;s scheduled the work to happen in the background, and then given you a handle object to join the work later (if you want). In terms of control flow semantics, this is just like spawning a thread. Then you register callbacks on the promise, so see the previous bullet point.</p></li></ul></blockquote><h3>The fork-join pattern</h3><p>Swift's structured concurrency takes a different approach: a pattern called <strong>fork-join</strong>. The idea is simple &#8212; multiple operations start running concurrently (the <em>fork</em>), and then their results are combined once they all complete (the <em>join</em>). <em>async let</em> and <em>TaskGroup</em> are the two APIs that implement this pattern, and they differ along one key axis: whether the number of operations you want to fork is known at compile time (<em>static width</em>) or determined at runtime (<em>dynamic width</em>).</p><h4><code>async let</code> &#8212; static width</h4><p><em>async let</em> is fork-join for a fixed, known number of operations. If you have a predefined number of operations that always need to be performed together, you simply declare them one after the other. Each <em>async let</em> declaration is itself a fork point &#8212; the right-hand side begins executing as a child task the moment you write <em>async let</em>. The join happens when you <em>await</em> the binding:</p><p>swift</p><pre><code><code>func loadProfile() async -&gt; Profile {
    async let user = fetchUser()        // fork
    async let posts = fetchPosts()      // fork
    async let avatar = fetchAvatar()    // fork

    return await Profile(               // join
        user: user,
        posts: posts,
        avatar: avatar
    )
}</code></code></pre><p>All three fetches are in flight by the time we reach the <em>return</em>. The <em>await</em> is the join site &#8212; it&#8217;s where we wait for the children to finish and collect their results. The width here is static: there are exactly three child tasks, hardcoded into the source. You couldn&#8217;t write this with a variable number of fetches without restructuring.</p><h4><code>TaskGroup</code> &#8212; dynamic width</h4><p><em>TaskGroup</em> is fork-join for a runtime-determined number of operations. When the count or the work depends on values you only have at runtime, you open a group and add a child task for each unit of work you want to run. Each <em>addTask</em> call is a fork; the join occurs as you iterate over the group's results:</p><p>swift</p><pre><code><code>func loadPosts(for ids: [UUID]) async -&gt; [Post] {
    await withTaskGroup(of: Post.self) { group in
        for id in ids {
            group.addTask { await fetchPost(id: id) }   // fork
        }

        var posts: [Post] = []
        for await post in group {                       // join
            posts.append(post)
        }
        return posts
    }
}</code></code></pre><p>The shape is the same &#8212; fork some children, join their results &#8212; but the count is determined by <em>ids.count</em> at runtime rather than baked into the source. If <em>ids</em> has three elements you get three child tasks; if it has three hundred, you get three hundred.</p><h3><code>Conclusion</code></h3><p>The static/dynamic distinction is usually the deciding factor on which to use. If you know at the call site exactly which operations you want to run concurrently, use <em>async let</em> &#8212; each child gets its own typed binding, and you read the results back by name. If the count or the work depends on runtime data, use <em>TaskGroup</em>, at the cost of homogeneous child result types and a bit more setup boilerplate.</p><p>Conceptually, though, they&#8217;re much closer than they first appear. Both APIs implement the same fork-join structure: child tasks are forked from a parent task, execute concurrently within that parent&#8217;s scope, and are joined before the scope completes. The difference is not in the underlying concurrency model, but in how the number of forks is expressed. Either way, you&#8217;re expressing the same idea: fork some work to run concurrently, then join the results when you need them.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading jbakerlent! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Why Async/Await Isn’t Just Prettier GCD]]></title><description><![CDATA[Continuations]]></description><link>https://swiftwright.dev/p/why-asyncawait-isnt-just-prettier</link><guid isPermaLink="false">https://swiftwright.dev/p/why-asyncawait-isnt-just-prettier</guid><dc:creator><![CDATA[John]]></dc:creator><pubDate>Sat, 02 May 2026 23:57:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cSw4!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59075462-3aa3-4185-ab25-4ae020813b48_1254x1254.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When writing asynchronous code using async/await, the most obvious improvements are syntactical ones &#8212; being able to read the code top-to-bottom and see the execution order, not dealing with masses of completion handlers or complex error handling, etc.</p><p>But the benefits go much deeper on the performance side.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading John's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>The GCD Model</h3><p>When GCD was introduced way back with Snow Leopard in 2009, thread pools were the right answer (or so I&#8217;m told haha). When a thread is busy or blocked, GCD simply spawns another thread to pick up later work.</p><p>In practice, this leads to two significant problems: an explosion in the number of active threads and a related slowdown from having to hop between them. Remember that devices have a fixed number of actual CPU cores &#8212; 6 on recent iPhones &#8212; so having to time slice between, say, 20 threads to ensure they can all progress doesn&#8217;t give you the performance gain you might imagine.</p><p>Each time execution shifts between threads, it must perform a <strong>context switch</strong>, which is a kernel-level operation involving saving all of the current thread&#8217;s state and loading and resuming the new thread. On top of this, pulling in the new state pollutes the CPU&#8217;s cache (L1, L2), causing further slowdowns by forcing values to be fetched from slower locations.</p><h3>Enter Continuations</h3><p>With async/await (and Swift Concurrency more broadly) these problems are avoided by adopting some <a href="https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619">prior art from C#</a> (among other sources).</p><p>Instead of spawning an unbounded number of new threads, the runtime uses a fixed pool of threads roughly equal to the number of CPU cores. And to avoid the slowdown from context switches, it uses a mechanism known as a <strong>continuation</strong>.</p><p>Continuations are essentially portable stack frames &#8212; a snapshot of the function&#8217;s state and local variables that can be paused and resumed elsewhere. Instead of blocking a thread (which forces a context switch to keep work flowing), the function suspends by saving its state into a continuation and simply returns. The thread is immediately free to run other work &#8212; no kernel involvement, no context switch, no cache wipe. As threads are freed up, they can cheaply pick up any available continuations and continue execution.</p><h3>A Restaurant Analogy</h3><p>Hopefully this helps visualize what we&#8217;ve been talking about.</p><p><strong>GCD:</strong> If a waiter (thread) is waiting for food (an API call), they just stand at the window doing nothing. If more orders come in, the manager hires more waiters (thread explosion). Soon the kitchen is too crowded to move (context switching).</p><p><strong>Async/Await:</strong> When the food isn&#8217;t ready, the waiter puts a &#8220;post-it note&#8221; (continuation) on the counter and goes to help another table. The manager never hires more waiters than there are tables.</p><h3>The Takeaway</h3><p>Async/await isn&#8217;t just nicer syntax than GCD &#8212; it&#8217;s a fundamentally different concurrency primitive. The readability is a bonus.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://swiftwright.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading John's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>