<?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: Vinayak G Hejib</title>
    <description>The latest articles on DEV Community by Vinayak G Hejib (@vnayak_hejib).</description>
    <link>https://dev.to/vnayak_hejib</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%2F3287815%2Fc98117cb-7e0c-4c88-81cc-988f590f6dee.png</url>
      <title>DEV Community: Vinayak G Hejib</title>
      <link>https://dev.to/vnayak_hejib</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vnayak_hejib"/>
    <language>en</language>
    <item>
      <title>I Let AI Build an Entire Mobile App Feature. Then I Had to Debug It.</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Fri, 21 Aug 2026 16:34:06 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/i-let-ai-build-an-entire-mobile-app-feature-then-i-had-to-debug-it-1okb</link>
      <guid>https://dev.to/vnayak_hejib/i-let-ai-build-an-entire-mobile-app-feature-then-i-had-to-debug-it-1okb</guid>
      <description>&lt;p&gt;&lt;em&gt;What happens when you stop writing the code and let an AI coding agent build a real mobile feature? I put it to the test, with most of the experiment focused on Swift and iOS.&lt;/em&gt;&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Focyt3d4mvwzdxovoclmc.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Focyt3d4mvwzdxovoclmc.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been using AI-assisted development for quite some time now.&lt;/p&gt;

&lt;p&gt;Like many developers, I've gone from using AI to explain code, to generating boilerplate, to debugging errors, to asking it to refactor code, write tests, and increasingly, to letting coding agents work across multiple files.&lt;/p&gt;

&lt;p&gt;And that got me thinking about a slightly uncomfortable question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happens if I stop telling the AI how to implement a feature and simply ask it to build one?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not a toy application.&lt;/p&gt;

&lt;p&gt;Not a "Hello World" example.&lt;/p&gt;

&lt;p&gt;Not a screen with a button and a network call.&lt;/p&gt;

&lt;p&gt;A reasonably realistic mobile feature with asynchronous work, caching, cancellation, UI state, error handling, and tests.&lt;/p&gt;

&lt;p&gt;So I decided to try an experiment.&lt;/p&gt;

&lt;p&gt;I would give an AI coding agent a specification.&lt;/p&gt;

&lt;p&gt;I would let it make the implementation decisions.&lt;/p&gt;

&lt;p&gt;And then I would review the result like I would review a pull request from another engineer.&lt;/p&gt;

&lt;p&gt;The interesting part wasn't whether the AI could produce code.&lt;/p&gt;

&lt;p&gt;I already knew it could.&lt;/p&gt;

&lt;p&gt;The interesting part was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Could it produce code that I would actually be comfortable maintaining?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's where things got interesting.&lt;/p&gt;




&lt;h1&gt;
  
  
  The experiment
&lt;/h1&gt;

&lt;p&gt;I'm framing this as a &lt;strong&gt;mobile development&lt;/strong&gt; experiment, but most of the implementation and debugging examples are deliberately focused on &lt;strong&gt;iOS and Swift&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reason is simple: that's where I spend most of my engineering time, and Swift's concurrency and SwiftUI model provide some particularly interesting places for AI-generated code to go wrong.&lt;/p&gt;

&lt;p&gt;The experiment has a simple structure:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbcmbgb6p4aqvt592btdf.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbcmbgb6p4aqvt592btdf.png" alt=" " width="457" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal isn't to prove that AI is bad at programming.&lt;/p&gt;

&lt;p&gt;Quite the opposite.&lt;/p&gt;

&lt;p&gt;The goal is to understand &lt;strong&gt;where AI is genuinely useful and where engineering judgment still matters&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  The rules
&lt;/h1&gt;

&lt;p&gt;To make the experiment meaningful, I wanted to remove as much hand-holding as possible.&lt;/p&gt;

&lt;p&gt;The AI gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a feature specification&lt;/li&gt;
&lt;li&gt;the existing project structure&lt;/li&gt;
&lt;li&gt;access to the relevant codebase&lt;/li&gt;
&lt;li&gt;the ability to create and modify files&lt;/li&gt;
&lt;li&gt;the ability to run tests&lt;/li&gt;
&lt;li&gt;the ability to build the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I deliberately don't give it the implementation.&lt;/p&gt;

&lt;p&gt;In other words, I don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Use an actor here."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Use MVVM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Use an async image loader."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Use a dictionary to deduplicate requests."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, I give it the &lt;strong&gt;problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;Because otherwise I'm not really testing the AI's engineering ability.&lt;/p&gt;

&lt;p&gt;I'm testing its ability to follow my instructions.&lt;/p&gt;




&lt;h1&gt;
  
  
  The feature
&lt;/h1&gt;

&lt;p&gt;I wanted something small enough to understand but complex enough to expose engineering problems.&lt;/p&gt;

&lt;p&gt;So let's build a product search feature.&lt;/p&gt;

&lt;p&gt;The requirements are:&lt;/p&gt;

&lt;h3&gt;
  
  
  Functional requirements
&lt;/h3&gt;

&lt;p&gt;The application should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allow the user to enter a search query.&lt;/li&gt;
&lt;li&gt;Fetch products from a remote API.&lt;/li&gt;
&lt;li&gt;Display results in a scrolling list.&lt;/li&gt;
&lt;li&gt;Load product images asynchronously.&lt;/li&gt;
&lt;li&gt;Cache previously loaded images.&lt;/li&gt;
&lt;li&gt;Avoid duplicate image downloads.&lt;/li&gt;
&lt;li&gt;Support pagination.&lt;/li&gt;
&lt;li&gt;Cancel unnecessary requests.&lt;/li&gt;
&lt;li&gt;Show loading, empty, and error states.&lt;/li&gt;
&lt;li&gt;Retry failed requests.&lt;/li&gt;
&lt;li&gt;Work correctly when the user rapidly changes the search query.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Non-functional requirements
&lt;/h3&gt;

&lt;p&gt;The implementation should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use Swift concurrency&lt;/li&gt;
&lt;li&gt;be testable&lt;/li&gt;
&lt;li&gt;avoid unnecessary network calls&lt;/li&gt;
&lt;li&gt;avoid obvious memory leaks&lt;/li&gt;
&lt;li&gt;handle cancellation correctly&lt;/li&gt;
&lt;li&gt;maintain a responsive UI&lt;/li&gt;
&lt;li&gt;separate UI concerns from networking&lt;/li&gt;
&lt;li&gt;avoid putting business logic directly into the SwiftUI view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's enough.&lt;/p&gt;

&lt;p&gt;I don't tell the AI how to achieve any of it.&lt;/p&gt;




&lt;h1&gt;
  
  
  The first prompt
&lt;/h1&gt;

&lt;p&gt;The actual prompt can be surprisingly simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a production-quality product search feature for this iOS application.

Requirements:

- Search products using a remote API.
- Display results in a SwiftUI list.
- Support pagination.
- Load product images asynchronously.
- Cache images.
- Deduplicate simultaneous requests for the same image URL.
- Cancel obsolete network requests.
- Handle loading, empty, error and retry states.
- Rapidly changing search queries must not result in stale results being displayed.
- The implementation must be testable.
- Follow the existing project's architecture and conventions.
- Use Swift concurrency.
- Do not introduce unnecessary dependencies.

First inspect the existing project structure and identify the appropriate place for this feature.

Implement the feature completely.

Run the relevant tests and fix compilation/test failures.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then I stepped back.&lt;/p&gt;




&lt;h1&gt;
  
  
  First surprise: AI is really good at the first 80%
&lt;/h1&gt;

&lt;p&gt;The first thing that's easy to underestimate is how much code an AI coding agent can produce very quickly.&lt;/p&gt;

&lt;p&gt;Within a relatively short amount of time, you can get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SearchView
SearchViewModel
ProductRepository
APIClient
ImageLoader
ImageCache
Models
Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw4qz6qyr3qsquigo3azk.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw4qz6qyr3qsquigo3azk.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can wire the pieces together.&lt;/p&gt;

&lt;p&gt;It can generate models.&lt;/p&gt;

&lt;p&gt;It can create async networking code.&lt;/p&gt;

&lt;p&gt;It can write SwiftUI.&lt;/p&gt;

&lt;p&gt;It can generate tests.&lt;/p&gt;

&lt;p&gt;It can run the compiler.&lt;/p&gt;

&lt;p&gt;It can fix obvious compiler errors.&lt;/p&gt;

&lt;p&gt;And this is where the AI experience feels almost magical.&lt;/p&gt;

&lt;p&gt;You go from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Here's the requirement."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Here's a working implementation."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;much faster than traditional development.&lt;/p&gt;

&lt;p&gt;But "working" and "production-ready" are very different things.&lt;/p&gt;




&lt;h1&gt;
  
  
  The first code review
&lt;/h1&gt;

&lt;p&gt;This is where I stopped looking at the AI as a code generator.&lt;/p&gt;

&lt;p&gt;I started looking at it as an engineer submitting a pull request.&lt;/p&gt;

&lt;p&gt;And I asked the same questions I would ask during any serious code review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the invariants?&lt;/li&gt;
&lt;li&gt;Where is state owned?&lt;/li&gt;
&lt;li&gt;What happens when requests overlap?&lt;/li&gt;
&lt;li&gt;What happens when work is cancelled?&lt;/li&gt;
&lt;li&gt;What happens when an async operation suspends?&lt;/li&gt;
&lt;li&gt;Can stale data overwrite fresh data?&lt;/li&gt;
&lt;li&gt;Can two callers trigger the same network request?&lt;/li&gt;
&lt;li&gt;What happens when the view disappears?&lt;/li&gt;
&lt;li&gt;What happens under memory pressure?&lt;/li&gt;
&lt;li&gt;Are the tests testing behavior or implementation?&lt;/li&gt;
&lt;li&gt;What happens when the network fails halfway through pagination?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And suddenly, the amount of code wasn't the interesting part anymore.&lt;/p&gt;

&lt;p&gt;The interesting part was the &lt;strong&gt;reasoning behind the code&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Problem #1: "But it's an actor, so we're thread-safe... right?"
&lt;/h1&gt;

&lt;p&gt;This is one of the most interesting places where Swift concurrency can fool you.&lt;/p&gt;

&lt;p&gt;Suppose the AI creates an image loader:&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;actor&lt;/span&gt; &lt;span class="kt"&gt;ImageLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;existingTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;existingTask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&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;At first glance, this looks pretty good.&lt;/p&gt;

&lt;p&gt;It's an actor.&lt;/p&gt;

&lt;p&gt;The dictionary is protected.&lt;/p&gt;

&lt;p&gt;Concurrent access is isolated.&lt;/p&gt;

&lt;p&gt;Requests for the same URL are deduplicated.&lt;/p&gt;

&lt;p&gt;Done?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;The important thing about actors is not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Only one thing can access this data."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more important concept is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An actor can suspend, and when it resumes, the state may no longer be what it was before the suspension.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's actor reentrancy.&lt;/p&gt;

&lt;p&gt;Imagine:&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;func&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;existingTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;existingTask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The actor can process other messages while this operation is suspended.&lt;/p&gt;

&lt;p&gt;That means the mental model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enter actor
↓
nothing else can happen
↓
leave actor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is incorrect.&lt;/p&gt;

&lt;p&gt;The better mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enter actor
↓
read state
↓
await
↓
release actor
↓
other work can execute
↓
resume
↓
state may have changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is subtle.&lt;/p&gt;

&lt;p&gt;And it is exactly the sort of thing that can slip through an AI-generated implementation because the code looks perfectly reasonable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why this matters outside image loading
&lt;/h1&gt;

&lt;p&gt;This isn't just an image-loader problem.&lt;/p&gt;

&lt;p&gt;The same reasoning applies to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;token refresh&lt;/li&gt;
&lt;li&gt;request deduplication&lt;/li&gt;
&lt;li&gt;authentication state&lt;/li&gt;
&lt;li&gt;database operations&lt;/li&gt;
&lt;li&gt;pagination&lt;/li&gt;
&lt;li&gt;task registries&lt;/li&gt;
&lt;li&gt;cache updates&lt;/li&gt;
&lt;li&gt;connection managers&lt;/li&gt;
&lt;li&gt;download managers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever you have:&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;actor&lt;/span&gt; &lt;span class="kt"&gt;Something&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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;State&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// read state&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// assume state is unchanged&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;you should ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What could have changed while I was suspended?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question is often more valuable than asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is this code using actors?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Problem #2: Cancellation that doesn't actually cancel the work
&lt;/h1&gt;

&lt;p&gt;Another common AI-generated pattern looks like this:&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;func&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;query&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then somewhere in 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="n"&gt;searchTask&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;searchTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&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;Looks reasonable.&lt;/p&gt;

&lt;p&gt;The old task gets cancelled.&lt;/p&gt;

&lt;p&gt;The new task starts.&lt;/p&gt;

&lt;p&gt;But cancellation in Swift is &lt;strong&gt;cooperative&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Calling:&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="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't magically terminate every operation inside that task.&lt;/p&gt;

&lt;p&gt;The work has to observe cancellation.&lt;/p&gt;

&lt;p&gt;For example:&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="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkCancellation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&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="k"&gt;if&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isCancelled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And importantly, the underlying operation also needs to behave correctly when cancellation occurs.&lt;/p&gt;

&lt;p&gt;This becomes particularly important with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User types:
"i"
 ↓
request A

"ip"
 ↓
request B

"iph"
 ↓
request C

"iphone"
 ↓
request D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If requests A, B and C don't actually stop, you can have multiple operations running unnecessarily.&lt;/p&gt;

&lt;p&gt;Even worse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A ────────────────┐
                          ↓
Request D ──────────→ UI
                          ↑
Request A ────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now an old request can potentially interfere with newer state if the architecture isn't careful.&lt;/p&gt;




&lt;h1&gt;
  
  
  Problem #3: The stale response problem
&lt;/h1&gt;

&lt;p&gt;This is one of those bugs that looks fine during normal testing.&lt;/p&gt;

&lt;p&gt;Suppose the user searches:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then immediately:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The requests execute concurrently.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6n8nbom1e9mck1oi18gr.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6n8nbom1e9mck1oi18gr.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But network timing isn't deterministic.&lt;/p&gt;

&lt;p&gt;You could get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search "shoes"
       │
       ├───────────────→ Server
       │
Search "jackets"
       │
       ├──────→ Server
       │
       │
       │       jackets response
       │              ↓
       │             UI
       │
       │       shoes response
       │              ↓
       │             UI   ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI now shows shoes even though the current query is jackets.&lt;/p&gt;

&lt;p&gt;The AI can write perfectly valid asynchronous code and still miss the &lt;strong&gt;semantic race&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One solution is to associate requests with a generation/token:&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;requestID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;currentRequestID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestID&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;requestID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;currentRequestID&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another is to structure the task lifecycle so obsolete tasks are cancelled and stale results cannot be committed.&lt;/p&gt;

&lt;p&gt;The exact implementation depends on the architecture.&lt;/p&gt;

&lt;p&gt;The important lesson is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Concurrency correctness is not the same thing as compiler correctness.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The compiler can verify that your code respects actor isolation.&lt;/p&gt;

&lt;p&gt;It cannot tell you that your application displayed the results for the wrong search query.&lt;/p&gt;




&lt;h1&gt;
  
  
  Problem #4: The cache looked correct
&lt;/h1&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmkltb36iq1sfhklp9gg.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmkltb36iq1sfhklp9gg.png" alt=" " width="629" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI also generated a cache.&lt;/p&gt;

&lt;p&gt;Something along the lines of:&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;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ImageCache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&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="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;Again:&lt;/p&gt;

&lt;p&gt;Perfectly understandable.&lt;/p&gt;

&lt;p&gt;But a production cache isn't just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dictionary&amp;lt;URL, Data&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You immediately have questions:&lt;/p&gt;

&lt;h3&gt;
  
  
  How large can it become?
&lt;/h3&gt;

&lt;p&gt;What happens if the application displays thousands of images?&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens under memory pressure?
&lt;/h3&gt;

&lt;p&gt;Should everything remain in memory?&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this memory cache or disk cache?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What is the eviction policy?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What happens if two requests arrive simultaneously?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Do we cache failed requests?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What about HTTP cache headers?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What happens when a low-resolution image is replaced by a high-resolution image?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What happens if the same URL is requested by 20 cells at the same time?
&lt;/h3&gt;

&lt;p&gt;That last question brings us back to request deduplication.&lt;/p&gt;

&lt;p&gt;The real system looks more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Image Request
                       │
                       ▼
                 Memory Cache
                    │     │
                 hit│     │miss
                    │     ▼
                    │  In-flight
                    │   Request
                    │     │
                    │     ▼
                    │   Disk/HTTP
                    │     │
                    │     ▼
                    │   Network
                    │
                    ▼
                  Image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code isn't necessarily difficult.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;state machine&lt;/strong&gt; is.&lt;/p&gt;




&lt;h1&gt;
  
  
  Problem #5: The tests looked impressive
&lt;/h1&gt;

&lt;p&gt;This was perhaps one of the more interesting observations.&lt;/p&gt;

&lt;p&gt;AI is extremely good at generating tests that &lt;em&gt;look&lt;/em&gt; like tests.&lt;/p&gt;

&lt;p&gt;For example:&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;func&lt;/span&gt; &lt;span class="nf"&gt;testSearchReturnsProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sut&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shoes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kt"&gt;XCTAssertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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;Great.&lt;/p&gt;

&lt;p&gt;But what behavior does this actually protect?&lt;/p&gt;

&lt;p&gt;Not much.&lt;/p&gt;

&lt;p&gt;The important tests are often the uncomfortable ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ successful request

✓ empty response

✓ network failure

✓ retry

✓ cancellation

✓ rapid query changes

✓ stale response

✓ duplicate image requests

✓ concurrent image requests

✓ pagination failure

✓ pagination cancellation

✓ view disappears during request

✓ request completes after cancellation

✓ cache hit

✓ cache miss

✓ cache eviction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And suddenly the test suite becomes a lot more interesting.&lt;/p&gt;




&lt;h1&gt;
  
  
  The difference between code coverage and confidence
&lt;/h1&gt;

&lt;p&gt;This is an important distinction.&lt;/p&gt;

&lt;p&gt;You can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;95% code coverage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and still have a broken application.&lt;/p&gt;

&lt;p&gt;Coverage answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did this code execute?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It doesn't necessarily answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did this system behave correctly under adverse conditions?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For concurrent systems, that's a huge distinction.&lt;/p&gt;

&lt;p&gt;You want tests around &lt;strong&gt;state transitions&lt;/strong&gt;, not just lines of code.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idle
  ↓
loading
  ↓
success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is one path.&lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idle
  ↓
loading
  ↓
cancelled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is another.&lt;/p&gt;

&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loading
  ↓
new search
  ↓
old request completes
  ↓
stale response discarded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is another.&lt;/p&gt;

&lt;p&gt;The second category is where a lot of real bugs live.&lt;/p&gt;




&lt;h1&gt;
  
  
  What the AI did surprisingly well
&lt;/h1&gt;

&lt;p&gt;This experiment isn't about proving that AI is bad.&lt;/p&gt;

&lt;p&gt;There were plenty of things it did extremely well.&lt;/p&gt;

&lt;p&gt;AI is particularly powerful at:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Boilerplate
&lt;/h3&gt;

&lt;p&gt;Models, DTOs, initial views, protocols, test scaffolding.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Repetitive transformations
&lt;/h3&gt;

&lt;p&gt;Renaming, moving code, extracting types, updating call sites.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. First-pass implementations
&lt;/h3&gt;

&lt;p&gt;Getting from zero to something compilable is dramatically faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. API exploration
&lt;/h3&gt;

&lt;p&gt;It can quickly suggest different approaches and explain unfamiliar APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test scaffolding
&lt;/h3&gt;

&lt;p&gt;It can generate a large amount of initial test structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Debugging compiler errors
&lt;/h3&gt;

&lt;p&gt;This is one of the areas where AI feels almost unfairly good.&lt;/p&gt;

&lt;p&gt;Give it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: Main actor-isolated property cannot be mutated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it can often get you surprisingly close to the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Documentation
&lt;/h3&gt;

&lt;p&gt;Turning implementation details into readable documentation is another excellent use.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where I still wanted a human engineer
&lt;/h1&gt;

&lt;p&gt;The interesting gap wasn't syntax.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;judgment&lt;/strong&gt;.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyaqppos8w4yfi6g4xae.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyaqppos8w4yfi6g4xae.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Consider these questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should this state be owned by the ViewModel?&lt;/p&gt;

&lt;p&gt;Should this operation be cancellable?&lt;/p&gt;

&lt;p&gt;Is this cache global or feature-scoped?&lt;/p&gt;

&lt;p&gt;Is this actor actually solving the problem?&lt;/p&gt;

&lt;p&gt;What happens if the user changes the query 10 times in one second?&lt;/p&gt;

&lt;p&gt;Should this request be retried?&lt;/p&gt;

&lt;p&gt;What happens when the app goes into the background?&lt;/p&gt;

&lt;p&gt;What happens under memory pressure?&lt;/p&gt;

&lt;p&gt;Is this architecture appropriate for a 5-year-old production application?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These aren't simply code-generation questions.&lt;/p&gt;

&lt;p&gt;They are &lt;strong&gt;system-design questions&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI is very good at local reasoning
&lt;/h1&gt;

&lt;p&gt;This is perhaps the biggest lesson from the experiment.&lt;/p&gt;

&lt;p&gt;AI can look at:&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;func&lt;/span&gt; &lt;span class="nf"&gt;loadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and produce a reasonable implementation.&lt;/p&gt;

&lt;p&gt;But the real question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What does this function mean to the rest of the system?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That requires broader context.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ImageLoader
     │
     ├── ProductCell
     │
     ├── SearchResults
     │
     ├── ProductDetails
     │
     ├── Recommendations
     │
     └── Wishlist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can I load an image?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Who owns image loading?"&lt;/p&gt;

&lt;p&gt;"Who owns caching?"&lt;/p&gt;

&lt;p&gt;"Can these features share requests?"&lt;/p&gt;

&lt;p&gt;"What is the lifetime of the cache?"&lt;/p&gt;

&lt;p&gt;"What happens when the user logs out?"&lt;/p&gt;

&lt;p&gt;"What happens when the CDN changes the image?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a very different level of reasoning.&lt;/p&gt;




&lt;h1&gt;
  
  
  The AI doesn't need to be perfect to be incredibly useful
&lt;/h1&gt;

&lt;p&gt;And this is where I think the conversation around AI coding often becomes unnecessarily binary.&lt;/p&gt;

&lt;p&gt;The choices aren't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI replaces developers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI is useless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a much more interesting middle ground.&lt;/p&gt;

&lt;p&gt;I increasingly think about AI coding agents like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Human
                      │
          ┌───────────┴───────────┐
          │                       │
      Judgment                Direction
          │                       │
          ▼                       ▼
       AI Agent ───────────→ Implementation
          │
          ▼
       Validation
          │
          ▼
        Human
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The human doesn't necessarily need to write every line.&lt;/p&gt;

&lt;p&gt;But the human needs to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which lines should exist in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  The role of the senior engineer changes
&lt;/h1&gt;

&lt;p&gt;This is probably the bigger question behind the whole experiment.&lt;/p&gt;

&lt;p&gt;If AI becomes capable of producing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;models
views
networking
tests
boilerplate
refactors
documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the value of an engineer increasingly shifts toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem definition
       ↓
System design
       ↓
Constraints
       ↓
Trade-offs
       ↓
Validation
       ↓
Debugging
       ↓
Observability
       ↓
Product judgment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The scarce skill may no longer be producing code. It may be knowing whether the code should exist.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  AI-generated code needs a stronger definition of "done"
&lt;/h1&gt;

&lt;p&gt;Traditionally, "done" might mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Compiles
✓ Tests pass
✓ Feature works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For AI-assisted development, I think we need a more demanding definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Compiles
✓ Tests pass
✓ Correct under concurrency
✓ Handles cancellation
✓ Handles failure
✓ Doesn't leak resources
✓ Doesn't introduce unnecessary complexity
✓ Meets architectural boundaries
✓ Has acceptable performance
✓ Is observable
✓ Is maintainable
✓ Doesn't violate security/privacy requirements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And perhaps most importantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ I understand why this code exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last check becomes incredibly important when the code wasn't written manually.&lt;/p&gt;




&lt;h1&gt;
  
  
  The uncomfortable question
&lt;/h1&gt;

&lt;p&gt;Here's the part I've been thinking about the most.&lt;/p&gt;

&lt;p&gt;If an AI agent can generate 80% of a feature...&lt;/p&gt;

&lt;p&gt;What happens to the engineer?&lt;/p&gt;

&lt;p&gt;I don't think the answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The engineer disappears."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think the answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The engineer moves up the abstraction stack.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of spending most of the time typing:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;ProductViewModel&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we may spend more time deciding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What should ProductViewModel own?

What should it not own?

Where does intelligence belong?

What are the concurrency invariants?

What are the failure modes?

How do we know this system is correct?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ironically, that could make &lt;strong&gt;deep engineering knowledge more valuable&lt;/strong&gt;, not less.&lt;/p&gt;

&lt;p&gt;Because if AI can write the obvious code, the differentiator becomes understanding the non-obvious code.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I would do differently next time
&lt;/h1&gt;

&lt;p&gt;After going through this exercise, I wouldn't simply ask an AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Build this feature."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'd give it a stronger engineering contract.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flua82uiw27qrsvs2yhos.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flua82uiw27qrsvs2yhos.png" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENGINEERING CONSTRAINTS

1. Use structured concurrency.
2. Cancellation must propagate correctly.
3. Do not use unstructured concurrency unless justified.
4. UI mutations must respect actor isolation.
5. Do not assume actor isolation eliminates logical races.
6. Network requests must be testable through dependency injection.
7. Duplicate requests for the same resource must be deduplicated.
8. Stale responses must never overwrite newer state.
9. Tests must cover failure and cancellation paths.
10. Do not introduce dependencies without justification.
11. Do not invent APIs.
12. Preserve existing architectural boundaries.
13. Explain architectural decisions before making large changes.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what happened.&lt;/p&gt;

&lt;p&gt;I didn't give the AI the implementation.&lt;/p&gt;

&lt;p&gt;I gave it the &lt;strong&gt;engineering principles&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's a much more interesting way of working with AI.&lt;/p&gt;




&lt;h1&gt;
  
  
  The future isn't "AI writes code"
&lt;/h1&gt;

&lt;p&gt;I think that framing is already becoming too simplistic.&lt;/p&gt;

&lt;p&gt;The more interesting future looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Human
                   │
            Requirements
                   │
                   ▼
             Specification
                   │
                   ▼
               AI Agent
                   │
          ┌────────┼────────┐
          ▼        ▼        ▼
       Design     Code     Tests
          │        │        │
          └────────┼────────┘
                   ▼
               Validation
                   │
                   ▼
              Human review
                   │
                   ▼
              Production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engineer becomes the person who establishes the boundaries within which the agent operates.&lt;/p&gt;

&lt;p&gt;And that's a very different skill from simply knowing how to type Swift.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;So, can AI build a mobile application feature?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Absolutely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can it produce surprisingly good code?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can it save a developer a huge amount of time?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without a doubt.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But can I look at a generated feature, see that it compiles, see that the tests pass, and immediately assume it is production-ready?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that's not necessarily a criticism of AI.&lt;/p&gt;

&lt;p&gt;It's a reminder that software engineering has never really been about writing code.&lt;/p&gt;

&lt;p&gt;Code is the output.&lt;/p&gt;

&lt;p&gt;The difficult part has always been understanding the system.&lt;/p&gt;

&lt;p&gt;Understanding state.&lt;/p&gt;

&lt;p&gt;Understanding failure.&lt;/p&gt;

&lt;p&gt;Understanding concurrency.&lt;/p&gt;

&lt;p&gt;Understanding trade-offs.&lt;/p&gt;

&lt;p&gt;Understanding what happens when the happy path disappears.&lt;/p&gt;

&lt;p&gt;And perhaps most importantly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Knowing what questions to ask before the bug happens.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the part I'm not ready to outsource.&lt;/p&gt;

&lt;p&gt;Not yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  One last thought
&lt;/h2&gt;

&lt;p&gt;Maybe the future senior engineer isn't the person who can write the most code.&lt;/p&gt;

&lt;p&gt;Maybe it's the person who can look at &lt;strong&gt;10,000 lines generated by an AI agent and know which 10 lines are going to hurt you six months from now.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if that's true, then learning how to work &lt;em&gt;with&lt;/em&gt; AI isn't about becoming better at prompting.&lt;/p&gt;

&lt;p&gt;It's about becoming a better engineer.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>ios</category>
      <category>programming</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>The Next Evolution of App Architecture: AI Just Introduced a New Orchestrator.</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Tue, 04 Aug 2026 16:19:18 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/the-next-evolution-of-app-architecture-ai-just-introduced-a-new-orchestrator-9na</link>
      <guid>https://dev.to/vnayak_hejib/the-next-evolution-of-app-architecture-ai-just-introduced-a-new-orchestrator-9na</guid>
      <description>&lt;h1&gt;
  
  
  Your architecture may not be dead but remember AI just introduced a new orchestrator
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; AI doesn't replace MVVM or anything compliant to it. &lt;br&gt;
It introduces a new orchestration layer. ViewModels still excel at managing UI state, but when your app needs to reason, plan, use tools, and maintain context, an &lt;strong&gt;Agent&lt;/strong&gt; becomes a natural addition to your architecture.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9bd2x6rm4lb0naal5ko4.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9bd2x6rm4lb0naal5ko4.webp" alt=" " width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift We Didn't See Coming
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft11i5xsjt1co3ge2vfgv.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft11i5xsjt1co3ge2vfgv.webp" alt=" " width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For over a decade, mobile architecture has focused on making UI code predictable and maintainable. Whether you follow MVC, MVP, VIPER, Clean Architecture, MVVM, or The Composable Architecture, the core idea has remained the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separate concerns.&lt;/li&gt;
&lt;li&gt;Keep business logic out of views.&lt;/li&gt;
&lt;li&gt;Make data flow predictable.&lt;/li&gt;
&lt;li&gt;Keep the UI reactive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical modern iOS application looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SwiftUI View
      │
      ▼
 ViewModel
      │
      ▼
 Repository
      │
      ▼
API / Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ViewModel receives user intent. The Repository fetches data. The View updates. Everything is deterministic, a button tap follows a predefined execution path.&lt;/p&gt;

&lt;p&gt;This architecture has served us incredibly well. But AI-powered experiences are fundamentally different.&lt;/p&gt;




&lt;h2&gt;
  
  
  Traditional Apps Execute
&lt;/h2&gt;

&lt;p&gt;Traditional applications execute instructions. For example, a user taps:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Search "Blue Jacket"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The flow is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;View → ViewModel → Repository → Search API → Products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every component knows exactly what to do. There is almost no ambiguity.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Applications Need to Think Before They Act
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4z2jhiegqj2zux0a0xqq.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4z2jhiegqj2zux0a0xqq.webp" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now consider a modern shopping experience. Instead of typing keywords, the user says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find me a blue waterproof jacket under ₹5,000 that's suitable for a rainy trip to Scotland next week."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice something interesting: the app cannot immediately call an API. It first needs to understand what the user actually means. It must infer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Waterproof&lt;/li&gt;
&lt;li&gt;Suitable for cold weather&lt;/li&gt;
&lt;li&gt;Budget constraint&lt;/li&gt;
&lt;li&gt;Destination&lt;/li&gt;
&lt;li&gt;Travel dates&lt;/li&gt;
&lt;li&gt;Product availability&lt;/li&gt;
&lt;li&gt;Ranking preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before any API is called, reasoning has already begun.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Request Can Become Multiple Tasks
&lt;/h2&gt;

&lt;p&gt;Internally, that single sentence may become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Understand intent
      ↓
Break problem into tasks
      ↓
Search products
      ↓
Check weather
      ↓
Understand destination climate
      ↓
Filter inventory
      ↓
Rank recommendations
      ↓
Generate explanation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That isn't something a traditional ViewModel was designed to orchestrate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ViewModel's Responsibility Hasn't Changed
&lt;/h2&gt;

&lt;p&gt;Many developers immediately ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isn't an Agent just another ViewModel?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a fair question. The answer is &lt;strong&gt;not quite&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A ViewModel's primary responsibility is still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage screen state&lt;/li&gt;
&lt;li&gt;Transform models into UI&lt;/li&gt;
&lt;li&gt;Handle user interactions&lt;/li&gt;
&lt;li&gt;Coordinate presentation logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those responsibilities remain exactly the same even in AI-powered applications. What changes is &lt;strong&gt;who coordinates reasoning&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter the Agent
&lt;/h2&gt;

&lt;p&gt;Think of an Agent as an orchestration layer. Instead of simply forwarding requests, it decides &lt;strong&gt;how&lt;/strong&gt; a request should be solved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
      ↓
   Agent
      ↓
  Reason
      ↓
   Plan
      ↓
Choose tools
      ↓
Execute tools
      ↓
Combine results
      ↓
Return response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Agent isn't replacing your networking layer. It isn't replacing repositories. It isn't replacing MVVM. &lt;strong&gt;It is coordinating them.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Mental Model
&lt;/h2&gt;

&lt;p&gt;Instead of thinking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ViewModel → Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;View → ViewModel → Agent → Tools → Repositories → Backend → LLM

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frwelkggzykdokt397o7y.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frwelkggzykdokt397o7y.webp" alt=" " width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ViewModel still owns UI state. The Agent owns intelligence.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Exactly Does an Agent Do?
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7im0iv2wuum9santpf4.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7im0iv2wuum9santpf4.webp" alt=" " width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Understand Intent
&lt;/h3&gt;

&lt;p&gt;The user's words are rarely explicit. Example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Something comfortable for office meetings."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Agent converts vague language into structured requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Planning
&lt;/h3&gt;

&lt;p&gt;Complex requests are broken into multiple smaller tasks. Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search products → Compare options → Remove unavailable items → Rank → Summarize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Tool Calling
&lt;/h3&gt;

&lt;p&gt;Modern LLMs don't perform every action themselves instead, they invoke tools. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product Search&lt;/li&gt;
&lt;li&gt;Weather API&lt;/li&gt;
&lt;li&gt;Calendar&lt;/li&gt;
&lt;li&gt;Maps&lt;/li&gt;
&lt;li&gt;Payment&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Inventory Service&lt;/li&gt;
&lt;li&gt;Recommendation Engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Agent decides which tools should be executed and in what order.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Memory
&lt;/h3&gt;

&lt;p&gt;Traditional apps remember UI state. Agents remember conversations. Example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Show me another one."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another what? The Agent knows, because it remembers previous context.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Reasoning
&lt;/h3&gt;

&lt;p&gt;Instead of returning raw API results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product A
Product B
Product C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may produce:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Product B is slightly more expensive, but it offers significantly better waterproofing, making it more suitable for your upcoming trip to Scotland."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's reasoning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparing Responsibilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional MVVM&lt;/th&gt;
&lt;th&gt;AI-Native Architecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ViewModel manages UI&lt;/td&gt;
&lt;td&gt;ViewModel still manages UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository fetches data&lt;/td&gt;
&lt;td&gt;Repositories still fetch data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business logic&lt;/td&gt;
&lt;td&gt;Business logic still exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API calls&lt;/td&gt;
&lt;td&gt;APIs still exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static flow&lt;/td&gt;
&lt;td&gt;Agent orchestrates dynamic workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen state&lt;/td&gt;
&lt;td&gt;Conversation state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI events&lt;/td&gt;
&lt;td&gt;User intent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic execution&lt;/td&gt;
&lt;td&gt;Planning and reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice how almost everything remains. We're simply introducing another orchestration layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Should the Agent Live?
&lt;/h2&gt;

&lt;p&gt;This is probably the most interesting architectural discussion. One possible design looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SwiftUI View
      ↓
  ViewModel
      ↓
    Agent
   ├── Memory
   ├── Planner
   ├── Tool Registry
   ├── Prompt Builder
   └── LLM Client
      ↓
 Repositories
      ↓
Backend APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps concerns well separated. The ViewModel doesn't suddenly become responsible for prompts, memory, or tool execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Clean Architecture?
&lt;/h2&gt;

&lt;p&gt;If you're using Clean Architecture, the Agent naturally belongs in the domain layer; think of it as another domain service. Your ViewModel simply delegates complex AI tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ViewModel → AgentService → Repositories → Tools → LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repositories continue to own data access. The Agent owns orchestration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;As AI features become more common, we're moving beyond simple request-response interactions. Applications are beginning to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Think&lt;/li&gt;
&lt;li&gt;Plan&lt;/li&gt;
&lt;li&gt;Reason&lt;/li&gt;
&lt;li&gt;Remember&lt;/li&gt;
&lt;li&gt;Decide&lt;/li&gt;
&lt;li&gt;Explain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That introduces responsibilities that traditional UI architectures were never intended to handle. Trying to push all of that into a ViewModel eventually leads to massive classes with mixed responsibilities. Adding an Agent layer keeps each component focused on what it does best.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Example
&lt;/h2&gt;

&lt;p&gt;Imagine building an AI-powered travel assistant. The user asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Plan my weekend trip to Coorg with good vegetarian restaurants, pleasant weather, and hotels under ₹6,000."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An Agent might:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand the destination.&lt;/li&gt;
&lt;li&gt;Fetch weather forecasts.&lt;/li&gt;
&lt;li&gt;Search hotels.&lt;/li&gt;
&lt;li&gt;Search restaurants.&lt;/li&gt;
&lt;li&gt;Rank options.&lt;/li&gt;
&lt;li&gt;Remove unavailable hotels.&lt;/li&gt;
&lt;li&gt;Generate an itinerary.&lt;/li&gt;
&lt;li&gt;Explain why each recommendation was chosen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your ViewModel doesn't need to know how those eight steps happen. It simply receives a structured response and updates the UI. That's a much cleaner separation of responsibilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;Just as networking logic gradually moved out of ViewControllers and into repositories and services, AI orchestration deserves its own home.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;ViewModel&lt;/strong&gt; remains responsible for presentation.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Repository&lt;/strong&gt; remains responsible for data.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Backend&lt;/strong&gt; remains responsible for persistence.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;LLM&lt;/strong&gt; remains responsible for reasoning.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Agent&lt;/strong&gt; becomes responsible for coordinating the entire workflow.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F135ygyhtigt8cx9k4ujc.webp" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F135ygyhtigt8cx9k4ujc.webp" alt=" " width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't believe MVVM is becoming obsolete. In fact, it's still one of the best ways to manage presentation logic in SwiftUI.&lt;/p&gt;

&lt;p&gt;What is changing isn't the UI architecture, it's the layer &lt;strong&gt;above&lt;/strong&gt; it.&lt;/p&gt;

&lt;p&gt;The future of intelligent applications isn't about replacing ViewModels. It's about introducing an orchestration layer capable of understanding intent, planning workflows, invoking tools, maintaining context, and delivering intelligent experiences.&lt;/p&gt;

&lt;p&gt;The apps we build tomorrow won't just respond to user actions. They'll collaborate with users to accomplish goals. And that requires more than another ViewModel -&amp;gt; it requires an Agent.&lt;/p&gt;




&lt;h3&gt;
  
  
  What do you think?
&lt;/h3&gt;

&lt;p&gt;If you were designing an AI-first iOS application today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Would you extend your existing ViewModel?&lt;/li&gt;
&lt;li&gt;Would you introduce a dedicated Agent layer?&lt;/li&gt;
&lt;li&gt;Or do you see another architectural pattern emerging?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to hear how you're approaching AI-native application architecture.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mobile</category>
      <category>ios</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Beyond Portrait and Landscape: Building Truly Resizable iOS Apps</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Fri, 03 Jul 2026 09:56:33 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/beyond-portrait-and-landscape-building-truly-resizable-ios-apps-117i</link>
      <guid>https://dev.to/vnayak_hejib/beyond-portrait-and-landscape-building-truly-resizable-ios-apps-117i</guid>
      <description>&lt;h3&gt;
  
  
  &lt;em&gt;Apple: "Your app supports Portrait and Landscape, right?"&lt;/em&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Also Apple: "Cute. Now make it work at literally any window size."&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;_&lt;br&gt;
&lt;em&gt;Why every iOS developer needs to rethink layout architecture in the era of adaptive windows.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You've spent months perfecting your iPhone application.&lt;/p&gt;

&lt;p&gt;It looks fantastic on the iPhone SE, the iPhone 16, the Pro Max, and iPad. You ship. Everything works.&lt;/p&gt;

&lt;p&gt;Then Apple pushes the interaction model further freeform resizing, Stage Manager, external displays, floating windows and users start dragging your app's window to whatever size fits their workflow.&lt;/p&gt;

&lt;p&gt;Suddenly, it becomes like in image below:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48ov3yi3shfghwll2yp3.jpeg" 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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48ov3yi3shfghwll2yp3.jpeg" alt=" " width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing technically &lt;em&gt;breaks&lt;/em&gt;. No crash, no constraint warning in the console. But everything &lt;strong&gt;feels&lt;/strong&gt; broken because the layout was designed for &lt;em&gt;screens&lt;/em&gt;, not &lt;em&gt;windows&lt;/em&gt;, and that distinction is about to matter a lot more than it used to.&lt;/p&gt;

&lt;p&gt;For over fifteen years, iOS developers have thought in terms of devices: iPhone, iPad, portrait, landscape. The next era of Apple platforms demands a different mental model one built around &lt;strong&gt;available space&lt;/strong&gt;, not device identity.&lt;/p&gt;

&lt;p&gt;This article walks through that mindset shift and builds a production-ready, adaptive product detail page (PDP) that gracefully reflows as its window changes size including a custom SwiftUI &lt;code&gt;Layout&lt;/code&gt; implementation you can drop straight into a real app.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;For most of iOS history, your app's canvas was effectively fixed at runtime. Rotation gave you two known layouts, and that was the extent of your "adaptive" surface area.&lt;/p&gt;

&lt;p&gt;That assumption is breaking down. Modern Apple platforms increasingly embrace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Split View and Slide Over&lt;/strong&gt; on iPad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage Manager&lt;/strong&gt;, with genuinely freeform window sizing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External and wireless displays&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiwindow&lt;/strong&gt; scenes on iPadOS and visionOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuously variable widths&lt;/strong&gt;, not two or three fixed states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users don't think about which device they're on. They think about whether your app behaves naturally in whatever space they've given it. Supporting "smaller widths" as an edge case isn't enough anymore the goal is continuous adaptation across the entire width spectrum, not just graceful degradation at the extremes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Biggest Mistake Most Apps Make
&lt;/h2&gt;

&lt;p&gt;Here's the pattern nearly every app reaches for first:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;horizontalSizeClass&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;ProductMobileView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;ProductTabletView&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;It works until it doesn't. &lt;code&gt;horizontalSizeClass&lt;/code&gt; collapses an enormous range of real widths into exactly two buckets. But picture a window resizing continuously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;390 → 410 → 455 → 512 → 575 → 612 → 640 → 700 → 790 → 850
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your UI doesn't have two states. It has hundreds. Binary size-class branching leaves a massive, awkward gap between "phone" and "tablet" where the layout is neither optimized nor intentional it's just whichever of the two views got picked, stretched or squeezed to fit.&lt;/p&gt;

&lt;p&gt;The fix isn't a third &lt;code&gt;if&lt;/code&gt; branch. It's abandoning device-based branching as the primary layout mechanism altogether.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Example We'll Build
&lt;/h2&gt;

&lt;p&gt;We'll design a realistic e-commerce Product Detail Page (PDP) the kind you'd find in any modern retail app containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gallery&lt;/li&gt;
&lt;li&gt;Product name, brand, rating&lt;/li&gt;
&lt;li&gt;Price&lt;/li&gt;
&lt;li&gt;Colour picker / size picker&lt;/li&gt;
&lt;li&gt;Wishlist action&lt;/li&gt;
&lt;li&gt;Delivery information&lt;/li&gt;
&lt;li&gt;Description&lt;/li&gt;
&lt;li&gt;Reviews&lt;/li&gt;
&lt;li&gt;Recommended products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a toy example. It's representative of the layout complexity retail apps deal with daily, where imagery, metadata, actions, and recommendation carousels all have to coexist and reflow together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Point: The Naïve Layout
&lt;/h2&gt;

&lt;p&gt;Most implementations look like this:&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="kt"&gt;ScrollView&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="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ProductGallery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;ProductInformation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;SizeSelector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;ColorSelector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;AddToBagButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;Reviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kt"&gt;Recommendations&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 is fine on a phone. But as available width grows, it becomes wasteful long single-column scrolling, oceans of whitespace on either side, and a gallery that never gets to breathe next to the content it's describing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking in Layout Zones, Not Devices
&lt;/h2&gt;

&lt;p&gt;Instead of asking &lt;em&gt;"Am I on an iPhone?"&lt;/em&gt;, ask &lt;em&gt;"How much horizontal space do I actually have right now?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the fundamental shift. Rather than branching on device identity, define &lt;strong&gt;logical width ranges&lt;/strong&gt; that describe how content should be organized:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Width Range&lt;/th&gt;
&lt;th&gt;Experience&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Narrow&lt;/strong&gt; (&amp;lt; ~600pt)&lt;/td&gt;
&lt;td&gt;Single-column layout optimized for constrained space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Medium&lt;/strong&gt; (~600–900pt)&lt;/td&gt;
&lt;td&gt;Two-column layout gallery and details side by side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Wide&lt;/strong&gt; (&amp;gt; ~900pt)&lt;/td&gt;
&lt;td&gt;Multi-pane layout introducing recommendations or supplemental panes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are guidelines, not hard device categories the interface adapts continuously as the window changes, and the breakpoints themselves should be tuned against real content, not memorized from a spec sheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual Evolution
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhngmuy4ucbevy629i0i9.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhngmuy4ucbevy629i0i9.png" alt=" " width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice what &lt;em&gt;doesn't&lt;/em&gt; change: the content. Only its organization shifts. That's the tell of a well-architected adaptive layout the data and view models stay identical; only composition changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Before Code
&lt;/h2&gt;

&lt;p&gt;A common anti-pattern is embedding layout decisions directly inside views:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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="cm"&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 works initially but rots fast every screen ends up with its own bespoke breakpoint logic, and nobody remembers why &lt;code&gt;700&lt;/code&gt; was chosen versus &lt;code&gt;680&lt;/code&gt;.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2i6hku63fq92jdt1sfjw.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2i6hku63fq92jdt1sfjw.png" alt=" " width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A more scalable approach separates &lt;strong&gt;layout decision-making&lt;/strong&gt; from &lt;strong&gt;presentation&lt;/strong&gt;:&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;enum&lt;/span&gt; &lt;span class="kt"&gt;LayoutStyle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;narrow&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;medium&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;wide&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;narrow&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;medium&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wide&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 single source of truth determines the current &lt;code&gt;LayoutStyle&lt;/code&gt; from available width; the view hierarchy simply reacts to that style. This keeps your UI declarative, testable, and far easier to evolve you can unit test &lt;code&gt;LayoutStyle(width:)&lt;/code&gt; in isolation without spinning up a single view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Width the Right Way: GeometryReader vs. Container Values
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GeometryReader&lt;/code&gt; is the classic tool here, but it's a blunt instrument it greedily fills its parent and ignores the natural sizing of its children, which can quietly break layouts that worked fine before you added 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;struct&lt;/span&gt; &lt;span class="kt"&gt;AdaptiveProductPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&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;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;GeometryReader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LayoutStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;ProductPageContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;style&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;For most PDP-style screens this is perfectly adequate, since the page is already the root of its own scroll context. But if you're nesting adaptive components inside other adaptive components, prefer reading size via &lt;code&gt;.onGeometryChange(for:of:action:)&lt;/code&gt; (iOS 18+) or a &lt;code&gt;PreferenceKey&lt;/code&gt;-based width reporter, so children don't accidentally force their parents to expand:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;WidthReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Content&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;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;GeometryReader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;WidthKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&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="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onPreferenceChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;WidthKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$0&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="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;WidthKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreferenceKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;inout&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextValue&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;h2&gt;
  
  
  Composing the Layout
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;LayoutStyle&lt;/code&gt; established, the top-level view becomes a simple switch over composition, not appearance:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;ProductPageContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LayoutStyle&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;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ScrollView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;narrow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;narrowLayout&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;mediumLayout&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;wide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;wideLayout&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="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;narrowLayout&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;View&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="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ProductGallery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;ProductInformation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;SizeSelector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;AddToBagButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;Reviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;Recommendations&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="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mediumLayout&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;View&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="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;HStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;ProductGallery&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="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;ProductInformation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="kt"&gt;SizeSelector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="kt"&gt;AddToBagButton&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="kt"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kt"&gt;Reviews&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="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;wideLayout&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;View&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="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;HStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;ProductGallery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infinity&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="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;ProductInformation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="kt"&gt;SizeSelector&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="kt"&gt;AddToBagButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;340&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kt"&gt;Recommendations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="kt"&gt;Reviews&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;Each component &lt;code&gt;ProductGallery&lt;/code&gt;, &lt;code&gt;ProductInformation&lt;/code&gt;, &lt;code&gt;SizeSelector&lt;/code&gt;, &lt;code&gt;Recommendations&lt;/code&gt; knows nothing about where it will be placed. It only renders its own content. The composition layer decides arrangement. This separation of concerns means you can reorder, add, or remove regions without touching business logic or view models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: A Custom &lt;code&gt;Layout&lt;/code&gt; for True Fluidity
&lt;/h2&gt;

&lt;p&gt;Discrete breakpoints (&lt;code&gt;narrow&lt;/code&gt; / &lt;code&gt;medium&lt;/code&gt; / &lt;code&gt;wide&lt;/code&gt;) are a huge improvement over size-class branching, but they're still steps, not a continuum. For cases where you want genuinely fluid reflow say, a tag list or a filter chip row that should wrap based on exact available width SwiftUI's &lt;code&gt;Layout&lt;/code&gt; protocol (iOS 16+) is the right tool:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;FlowLayout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Layout&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;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sizeThatFits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ProposedViewSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;subviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Subviews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;inout&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CGSize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infinity&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rowWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;totalHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;subview&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subviews&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sizeThatFits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unspecified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rowWidth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;totalHeight&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;rowHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;
                &lt;span class="n"&gt;rowWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="n"&gt;rowHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;rowWidth&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;
            &lt;span class="n"&gt;rowHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;totalHeight&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;rowHeight&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;CGSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;totalHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;placeSubviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ProposedViewSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;subviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Subviews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;inout&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="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minX&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minY&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;subview&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subviews&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sizeThatFits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unspecified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minX&lt;/span&gt;
                &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;rowHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;
                &lt;span class="n"&gt;rowHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;subview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unspecified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;
            &lt;span class="n"&gt;rowHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&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;Drop this in for size pickers, filter chips, or delivery-option badges, and they'll wrap naturally at &lt;em&gt;any&lt;/em&gt; width no breakpoint table required. &lt;code&gt;Layout&lt;/code&gt; is also cheap: unlike &lt;code&gt;GeometryReader&lt;/code&gt;, it participates properly in SwiftUI's normal sizing negotiation instead of overriding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Forget &lt;code&gt;ViewThatFits&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For simpler cases swapping a compact control for a full one when space runs out &lt;code&gt;ViewThatFits&lt;/code&gt; (iOS 16+) is often all you need, and it's far less code than manual width math:&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="kt"&gt;ViewThatFits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;HStack&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;"Add to Bag"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"bag"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"bag"&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;SwiftUI tries each option in order and renders the first one that fits genuinely elegant for toolbar-style controls and buttons that need to shed content before they shed usefulness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing an Existing App
&lt;/h2&gt;

&lt;p&gt;If your app already ships to production, a full rewrite isn't necessary. A gradual migration tends to work best:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzkg0rawrw75841xwixak.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzkg0rawrw75841xwixak.png" alt=" " width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt; every place layout branches on device checks or orientation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; those checks with width-driven &lt;code&gt;LayoutStyle&lt;/code&gt; decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; reusable UI sections (gallery, info block, recommendations) into standalone components with no layout opinions of their own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introduce&lt;/strong&gt; an adaptive composition layer that arranges those components differently per &lt;code&gt;LayoutStyle&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test by resizing continuously&lt;/strong&gt; not just by toggling portrait/landscape in the simulator.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This incremental path minimizes risk while steadily raising the ceiling on how well your app handles space it wasn't originally designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Adaptive Layouts Properly
&lt;/h2&gt;

&lt;p&gt;A layout that "works" only at the exact widths you happened to test isn't adaptive it's coincidentally correct. A few practices that catch real issues early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Xcode Previews at multiple fixed widths.&lt;/strong&gt; Use &lt;code&gt;.previewLayout(.fixed(width:height:))&lt;/code&gt; across a spread of values (e.g. 320, 428, 600, 834, 1194) rather than relying on device presets alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resize the simulator window live&lt;/strong&gt; rather than only rotating it this is the one Split View and Stage Manager actually exercise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot tests at breakpoint boundaries&lt;/strong&gt;, not just breakpoint centers bugs cluster at the transition points (599pt vs. 601pt), not in the middle of a range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Type + resizing together.&lt;/strong&gt; A layout that survives resizing but breaks under XXL Dynamic Type isn't done test both axes simultaneously, since real users combine them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessibility Benefits
&lt;/h2&gt;

&lt;p&gt;Designing for resizing pays off beyond windowing scenarios. Users who increase Dynamic Type, enable Display Zoom, or work in Split View are already changing the effective space available to your app from your layout code's perspective, that's indistinguishable from a user resizing a window.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbmkgz0egjnkj6jzabiz.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbmkgz0egjnkj6jzabiz.png" alt=" " width="800" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An interface built to gracefully reorganize itself across widths is, almost as a side effect, more resilient to larger text, alternative input methods, and whatever form factor Apple ships next. Adaptive design and accessible design reinforce each other; you rarely get one without the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For years, iOS development revolved around screens. We memorized device dimensions, size classes, and orientation pairs.&lt;/p&gt;

&lt;p&gt;The next evolution asks for a different habit of mind not devices, but space; not portrait versus landscape, but fluid layouts that reorganize themselves intelligently as the width beneath them changes.&lt;/p&gt;

&lt;p&gt;Build from adaptable, layout-agnostic components rather than fixed screen assumptions, and your app becomes resilient not just to today's device lineup, but to whatever Apple introduces next.&lt;/p&gt;

&lt;p&gt;The question is no longer &lt;em&gt;"Does my app support iPhone and iPad?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's &lt;em&gt;"Can my interface make the best use of whatever space it's given?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That shift is what separates an app that merely runs everywhere from one that actually feels at home everywhere.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swiftui</category>
      <category>swift</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Building an AI-Powered Git Commit &amp; PR Assistant</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Fri, 15 May 2026 12:31:11 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/building-an-ai-powered-git-commit-pr-assistant-7ma</link>
      <guid>https://dev.to/vnayak_hejib/building-an-ai-powered-git-commit-pr-assistant-7ma</guid>
      <description>&lt;p&gt;Every engineer loves writing code(On a good note, I believe 😉).&lt;/p&gt;

&lt;p&gt;Not every engineer loves writing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commit messages&lt;/li&gt;
&lt;li&gt;PR descriptions&lt;/li&gt;
&lt;li&gt;testing notes&lt;/li&gt;
&lt;li&gt;release summaries&lt;/li&gt;
&lt;li&gt;sprint updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fast-moving teams, these things usually become an afterthought.&lt;/p&gt;

&lt;p&gt;And honestly, it shows.&lt;/p&gt;

&lt;p&gt;We’ve all seen commits like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fixed the bug 
changes into MyView file
final-final-fix
ui updates on list view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or some PR descriptions that simply say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Please review.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The bigger the codebase gets, the worse this problem becomes.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8ggo88vekao2eq49pmk.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8ggo88vekao2eq49pmk.jpeg" alt=" " width="800" height="765"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In modular enterprise applications with multiple teams working in parallel, poor PR communication slows reviews, increases confusion, and creates release risks.&lt;/p&gt;

&lt;p&gt;So I started building something for myself:&lt;/p&gt;

&lt;p&gt;An AI-powered Git assistant that could understand code changes and automatically generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;meaningful commit messages&lt;/li&gt;
&lt;li&gt;structured PR summaries&lt;/li&gt;
&lt;li&gt;testing notes&lt;/li&gt;
&lt;li&gt;risk indicators&lt;/li&gt;
&lt;li&gt;release notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I wanted it to work &lt;em&gt;offline&lt;/em&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why I Built It
&lt;/h1&gt;

&lt;p&gt;Initially, this started as a tiny productivity experiment.&lt;/p&gt;

&lt;p&gt;I simply wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cleaner commit messages&lt;/li&gt;
&lt;li&gt;less repetitive writing&lt;/li&gt;
&lt;li&gt;faster PR creation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But after using it for a few weeks, I realized something interesting:&lt;/p&gt;

&lt;p&gt;The real value wasn’t automation.&lt;/p&gt;

&lt;p&gt;It was reducing &lt;strong&gt;cognitive overhead&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After spending hours solving architectural or UI problems, context-switching into documentation mode becomes mentally expensive.&lt;/p&gt;

&lt;p&gt;The assistant helped bridge that gap.&lt;/p&gt;




&lt;h1&gt;
  
  
  High-Level Architecture
&lt;/h1&gt;

&lt;p&gt;The workflow is actually pretty straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git Diff
   ↓
File Analysis
   ↓
Context Extraction
   ↓
Prompt Generation
   ↓
Local LLM
   ↓
Commit + PR Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwln3k5cfcj4d9o7sda6g.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwln3k5cfcj4d9o7sda6g.jpeg" alt=" " width="800" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difficult part was making the outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concise&lt;/li&gt;
&lt;li&gt;trustworthy&lt;/li&gt;
&lt;li&gt;reviewer-friendly&lt;/li&gt;
&lt;li&gt;non-robotic&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Extracting Git Changes
&lt;/h1&gt;

&lt;p&gt;The first version simply passed the raw git diff directly into the model.&lt;/p&gt;

&lt;p&gt;That worked terribly.&lt;/p&gt;

&lt;p&gt;Large diffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exceeded context windows&lt;/li&gt;
&lt;li&gt;produced noisy summaries&lt;/li&gt;
&lt;li&gt;generated inaccurate PR descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I added preprocessing layers.&lt;/p&gt;

&lt;p&gt;Example:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_output&lt;/span&gt;&lt;span class="p"&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;git&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;diff&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;--cached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But raw diffs were too noisy.&lt;/p&gt;

&lt;p&gt;So the assistant started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grouping changes by module&lt;/li&gt;
&lt;li&gt;ignoring formatting-only modifications&lt;/li&gt;
&lt;li&gt;detecting API changes&lt;/li&gt;
&lt;li&gt;identifying added vs removed logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Filtering Noise
&lt;/h1&gt;

&lt;p&gt;One surprisingly important improvement was removing low-signal changes.&lt;/p&gt;

&lt;p&gt;For example:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_ignore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ignored_patterns&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;import &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;swiftlint&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;whitespace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ignored_patterns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dramatically improved the quality of generated summaries.&lt;/p&gt;




&lt;h1&gt;
  
  
  Prompt Engineering Was Harder Than Expected
&lt;/h1&gt;

&lt;p&gt;One thing I underestimated was how sensitive outputs were to prompting.&lt;/p&gt;

&lt;p&gt;A vague prompt generated vague PRs.&lt;/p&gt;

&lt;p&gt;An overly detailed prompt generated essays nobody wanted to read.&lt;/p&gt;

&lt;p&gt;Eventually I settled on prompts focused on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;behavioral changes&lt;/li&gt;
&lt;li&gt;architectural impact&lt;/li&gt;
&lt;li&gt;reviewer clarity&lt;/li&gt;
&lt;li&gt;testing implications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are an experienced software engineer reviewing a pull request.

Generate:
1. concise commit message
2. PR summary
3. testing notes
4. possible risks

Ignore formatting-only changes.

Git Diff:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;processed_diff&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ignore formatting-only changes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;improved results massively.&lt;/p&gt;




&lt;h1&gt;
  
  
  Running AI Offline
&lt;/h1&gt;

&lt;p&gt;This became the most interesting part of the project.&lt;/p&gt;

&lt;p&gt;I specifically wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local-first workflows&lt;/li&gt;
&lt;li&gt;zero cloud dependency&lt;/li&gt;
&lt;li&gt;privacy for enterprise repositories&lt;/li&gt;
&lt;li&gt;low latency&lt;/li&gt;
&lt;li&gt;no API costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sending proprietary diffs to external APIs was something I wanted to avoid entirely.&lt;/p&gt;

&lt;p&gt;So I experimented with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ollama&lt;/li&gt;
&lt;li&gt;llama.cpp&lt;/li&gt;
&lt;li&gt;quantized local models&lt;/li&gt;
&lt;li&gt;Apple Silicon optimizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Calling the local model was surprisingly simple:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/api/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&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;model&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;mistral&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;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&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;For commit generation and PR summaries, smaller local models were often more than sufficient.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Most Difficult Problems
&lt;/h1&gt;

&lt;p&gt;The difficult part wasn’t generating text.&lt;/p&gt;

&lt;p&gt;It was generating &lt;em&gt;trustworthy&lt;/em&gt; text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hallucinated Features
&lt;/h2&gt;

&lt;p&gt;Sometimes the model inferred functionality that didn’t exist.&lt;/p&gt;

&lt;p&gt;Especially when refactors looked similar to feature additions.&lt;/p&gt;

&lt;p&gt;To reduce this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompts became shorter&lt;/li&gt;
&lt;li&gt;context became more constrained&lt;/li&gt;
&lt;li&gt;diffs were chunked intelligently&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Huge Enterprise Diffs
&lt;/h2&gt;

&lt;p&gt;Large modular repositories create massive PRs.&lt;/p&gt;

&lt;p&gt;Passing entire diffs into the model quickly became inefficient.&lt;/p&gt;

&lt;p&gt;So I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chunking&lt;/li&gt;
&lt;li&gt;module prioritization&lt;/li&gt;
&lt;li&gt;high-signal file detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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="n"&gt;MAX_CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;

&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MAX_CHUNK_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;MAX_CHUNK_SIZE&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;h2&gt;
  
  
  Robotic Language
&lt;/h2&gt;

&lt;p&gt;Early outputs sounded overly AI-generated.&lt;/p&gt;

&lt;p&gt;Too many phrases like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“enhanced functionality”&lt;/li&gt;
&lt;li&gt;“optimized architecture”&lt;/li&gt;
&lt;li&gt;“improved user experience”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real engineers don’t write like that.&lt;/p&gt;

&lt;p&gt;So prompts were tuned toward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concise engineering tone&lt;/li&gt;
&lt;li&gt;direct wording&lt;/li&gt;
&lt;li&gt;reviewer readability&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Features That Became Surprisingly Useful
&lt;/h1&gt;

&lt;p&gt;The project slowly evolved beyond commit generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR Risk Detection
&lt;/h2&gt;

&lt;p&gt;The assistant flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shared module changes&lt;/li&gt;
&lt;li&gt;navigation flow modifications&lt;/li&gt;
&lt;li&gt;authentication updates&lt;/li&gt;
&lt;li&gt;API contract changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Automatic Testing Notes
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tested:
- Login flow
- Session recovery
- Token refresh handling
- Deep link navigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This alone ended up saving a surprising amount of time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Release Notes Generation
&lt;/h2&gt;

&lt;p&gt;The assistant can summarize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bug fixes&lt;/li&gt;
&lt;li&gt;user-facing improvements&lt;/li&gt;
&lt;li&gt;technical refactors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;directly from merged commits.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Output
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Before
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fixed auth issue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  After
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Refactor authentication recovery flow to support token refresh handling during session expiration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoabcejz2tgc1kj3g7ef.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoabcejz2tgc1kj3g7ef.jpeg" alt=" " width="800" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest realization from this project was:&lt;/p&gt;

&lt;p&gt;AI works best when augmenting engineering workflows, not replacing engineering decisions.&lt;/p&gt;

&lt;p&gt;The assistant is not writing code for me.&lt;/p&gt;

&lt;p&gt;It is removing repetitive cognitive work around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;communication&lt;/li&gt;
&lt;li&gt;formatting&lt;/li&gt;
&lt;li&gt;summarization&lt;/li&gt;
&lt;li&gt;workflow overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that turns out to be incredibly valuable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Future Improvements
&lt;/h1&gt;

&lt;p&gt;There’s still a lot I want to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Xcode integration&lt;/li&gt;
&lt;li&gt;Git hook automation&lt;/li&gt;
&lt;li&gt;reviewer suggestions&lt;/li&gt;
&lt;li&gt;Jira linking&lt;/li&gt;
&lt;li&gt;architecture drift detection&lt;/li&gt;
&lt;li&gt;PR quality scoring&lt;/li&gt;
&lt;li&gt;Slack release summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also want to experiment with embedding-based code understanding for better long-term context awareness.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Building AI tooling for toy projects is easy.&lt;/p&gt;

&lt;p&gt;Building AI tooling that works reliably in large modular enterprise repositories is a completely different challenge.&lt;/p&gt;

&lt;p&gt;But that’s also what makes it exciting.&lt;/p&gt;

&lt;p&gt;What started as a small commit-message helper slowly evolved into a developer workflow copilot that now saves me time almost every day.&lt;/p&gt;

&lt;p&gt;And honestly, this feels like just the beginning of what local AI can do for software engineering workflows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>git</category>
      <category>ios</category>
    </item>
    <item>
      <title># Unstructured Concurrency in iOS</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Thu, 12 Mar 2026 17:13:29 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/-unstructured-concurrency-in-ios-1pl7</link>
      <guid>https://dev.to/vnayak_hejib/-unstructured-concurrency-in-ios-1pl7</guid>
      <description>&lt;p&gt;A few months ago, I started noticing something strange in my app.&lt;/p&gt;

&lt;p&gt;Nothing was crashing.&lt;br&gt;
Nothing obvious was broken.&lt;/p&gt;

&lt;p&gt;But occasionally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  users saw stale data appear briefly &lt;/li&gt;
&lt;li&gt;  network requests were triggered twice &lt;/li&gt;
&lt;li&gt;  UI updates happened after a screen had already disappeared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Naturally, we checked the usual suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  race conditions &lt;/li&gt;
&lt;li&gt;  caching bugs &lt;/li&gt;
&lt;li&gt;  API inconsistencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything looked fine.&lt;/p&gt;

&lt;p&gt;Until we started tracing the execution paths.&lt;/p&gt;

&lt;p&gt;That's when we found them.&lt;/p&gt;

&lt;p&gt;Not one. &lt;br&gt;
Not two.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;dozens of tiny &lt;code&gt;Task {}&lt;/code&gt; blocks scattered across the codebase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each one looked harmless during code review. &lt;br&gt;
Each one worked perfectly in isolation.&lt;/p&gt;

&lt;p&gt;But together they created something far more subtle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;unstructured concurrency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And once we looked deeper, we discovered something even more&lt;br&gt;
interesting.&lt;/p&gt;

&lt;p&gt;Many of those tasks were also interacting with &lt;strong&gt;&lt;code&gt;@MainActor&lt;/code&gt;-isolated&lt;br&gt;
code&lt;/strong&gt; in ways that quietly turned the UI thread into a traffic&lt;br&gt;
bottleneck.&lt;/p&gt;

&lt;p&gt;No crashes.&lt;br&gt;
No compiler warnings.&lt;/p&gt;

&lt;p&gt;Just &lt;strong&gt;strange behavior that only appeared under real user&lt;br&gt;
interaction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The kind of bugs that make you stare at logs for hours.&lt;/p&gt;

&lt;p&gt;This article is about those bugs.&lt;/p&gt;

&lt;p&gt;And more importantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  why &lt;strong&gt;unstructured concurrency&lt;/strong&gt; often causes them &lt;/li&gt;
&lt;li&gt;  how &lt;strong&gt;&lt;code&gt;@MainActor&lt;/code&gt; can accidentally amplify them&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;  and how a few simple patterns can make Swift concurrency behave the
way it was actually designed to.&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  The Promise of Structured Concurrency
&lt;/h1&gt;

&lt;p&gt;Swift's concurrency model is built around a simple but powerful idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tasks should have a clear parent--child relationship.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When a parent task is cancelled, its children cancel automatically.&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;func&lt;/span&gt; &lt;span class="nf"&gt;loadProduct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchDetails&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchReviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reviews&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;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  both child tasks belong to &lt;code&gt;loadProduct&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  if the parent cancels → children cancel&lt;/li&gt;
&lt;li&gt;  errors propagate cleanly&lt;/li&gt;
&lt;li&gt;  lifetimes are predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is &lt;strong&gt;structured concurrency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of it like a well-managed engineering team: everyone knows who&lt;br&gt;
they report to.&lt;/p&gt;


&lt;h1&gt;
  
  
  Then Someone Writes This
&lt;/h1&gt;

&lt;p&gt;Sooner or later, someone writes:&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="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchProducts&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;Looks harmless. &lt;br&gt;
Feels convenient.&lt;/p&gt;

&lt;p&gt;But this is &lt;strong&gt;unstructured concurrency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  has &lt;strong&gt;no parent&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  has &lt;strong&gt;no ownership&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  has &lt;strong&gt;no automatic cancellation&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's basically a &lt;strong&gt;free‑roaming asynchronous creature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once created, it just... keeps living its life.&lt;/p&gt;


&lt;h1&gt;
  
  
  The Ghost Task Problem
&lt;/h1&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mu33bt73t0t00ms4qwh.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mu33bt73t0t00ms4qwh.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at a SwiftUI example.&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;struct&lt;/span&gt; &lt;span class="kt"&gt;ProductView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&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;View&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;"Products"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onAppear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadProducts&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;User opens the screen → task starts.&lt;/p&gt;

&lt;p&gt;User navigates away immediately → view disappears.&lt;/p&gt;

&lt;p&gt;But the task?&lt;/p&gt;

&lt;p&gt;Still running.&lt;/p&gt;

&lt;p&gt;Because that &lt;code&gt;Task {}&lt;/code&gt; has &lt;strong&gt;no relationship with the view lifecycle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Congratulations.&lt;/p&gt;

&lt;p&gt;You just created a &lt;strong&gt;ghost task&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Eventually it might finish and update state for a view that &lt;strong&gt;no longer&lt;br&gt;
exists&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These bugs are subtle, unpredictable, and extremely hard to trace.&lt;/p&gt;


&lt;h1&gt;
  
  
  The Accidental Parallelism Problem
&lt;/h1&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flysxhlsrywm7j416wurd.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flysxhlsrywm7j416wurd.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unstructured tasks also create hidden duplication.&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;func&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadData&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;User taps refresh twice quickly.&lt;/p&gt;

&lt;p&gt;Now you have &lt;strong&gt;two independent tasks&lt;/strong&gt; fetching the same data.&lt;/p&gt;

&lt;p&gt;Possible outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  duplicated requests&lt;/li&gt;
&lt;li&gt;  racing state updates&lt;/li&gt;
&lt;li&gt;  overwritten results&lt;/li&gt;
&lt;li&gt;  inflated analytics metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And during code review, nobody notices because:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It's just a &lt;code&gt;Task {}&lt;/code&gt;."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  The Uncancelled Work Problem
&lt;/h1&gt;

&lt;p&gt;A very common example appears in view models.&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;class&lt;/span&gt; &lt;span class="kt"&gt;ProductViewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&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;Looks perfectly normal.&lt;/p&gt;

&lt;p&gt;But the view model &lt;strong&gt;cannot cancel this task&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  the screen disappears &lt;/li&gt;
&lt;li&gt;  the user logs out &lt;/li&gt;
&lt;li&gt;  the view model is recreated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...the task keeps running anyway.&lt;/p&gt;

&lt;p&gt;You've effectively lost control over your concurrency.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Structured Alternative
&lt;/h1&gt;

&lt;p&gt;SwiftUI actually gives us a much better option.&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadProducts&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;The &lt;code&gt;.task&lt;/code&gt; modifier automatically ties the task to the &lt;strong&gt;view&lt;br&gt;
lifecycle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the view disappears:&lt;/p&gt;

&lt;p&gt;the task is &lt;strong&gt;cancelled automatically&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  Now Enter &lt;code&gt;@MainActor&lt;/code&gt;
&lt;/h1&gt;
&lt;h3&gt;
  
  
  And Why &lt;code&gt;@MainActor&lt;/code&gt; Is Both a Blessing... and a Trap
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu15k3222i2jqfg1xfu1u.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu15k3222i2jqfg1xfu1u.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Task {}&lt;/code&gt; is the most casually abused API in Swift concurrency, &lt;br&gt;
&lt;code&gt;@MainActor&lt;/code&gt; might be a close second.&lt;/p&gt;

&lt;p&gt;Before Swift concurrency, UI updates looked like this:&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="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titleLabel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swift improved this with:&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;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ProductViewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Product&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now UI updates are automatically safe.&lt;/p&gt;

&lt;p&gt;Clean. &lt;br&gt;
Elegant. &lt;br&gt;
Predictable.&lt;/p&gt;

&lt;p&gt;But like many convenient tools in software engineering...&lt;/p&gt;

&lt;p&gt;it's also easy to misuse.&lt;/p&gt;


&lt;h1&gt;
  
  
  The "Just Add @MainActor" Fix
&lt;/h1&gt;

&lt;p&gt;Soon someone sees a concurrency warning and writes:&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;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks harmless.&lt;/p&gt;

&lt;p&gt;But now the &lt;strong&gt;entire function executes on the main actor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which means orchestration begins on the UI executor.&lt;/p&gt;

&lt;p&gt;Multiply this across several view models and suddenly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  UI work &lt;/li&gt;
&lt;li&gt;  async orchestration &lt;/li&gt;
&lt;li&gt;  state mutation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...all compete on the same actor.&lt;/p&gt;

&lt;p&gt;Your main thread becomes a &lt;strong&gt;traffic intersection at rush hour&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  The MainActor ViewModel Trap
&lt;/h1&gt;

&lt;p&gt;Another common pattern:&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;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;FeedViewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&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;Now &lt;strong&gt;every method in the class runs on the main actor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Including things that shouldn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  JSON parsing&lt;/li&gt;
&lt;li&gt;  sorting large datasets&lt;/li&gt;
&lt;li&gt;  heavy data transformations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI thread quietly becomes your &lt;strong&gt;data processing engine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Scrolling performance will eventually protest.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Better Mental Model
&lt;/h1&gt;

&lt;p&gt;Think of &lt;code&gt;@MainActor&lt;/code&gt; as &lt;strong&gt;UI state protection&lt;/strong&gt;, not a concurrency&lt;br&gt;
shortcut.&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;class&lt;/span&gt; &lt;span class="kt"&gt;FeedViewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;@MainActor&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Post&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="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;MainActor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&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;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  networking happens off the main actor&lt;/li&gt;
&lt;li&gt;  computation happens off the main actor&lt;/li&gt;
&lt;li&gt;  only UI state touches the main actor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exactly what we want.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Both Problems Meet
&lt;/h1&gt;

&lt;p&gt;The most interesting bugs appear when &lt;strong&gt;unstructured tasks meet careless&lt;br&gt;
&lt;code&gt;@MainActor&lt;/code&gt; usage&lt;/strong&gt;.&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="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFeed&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;If &lt;code&gt;loadFeed()&lt;/code&gt; is &lt;code&gt;@MainActor&lt;/code&gt;, you now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  an &lt;strong&gt;unstructured task&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  executing on the &lt;strong&gt;main actor&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  doing work that might not belong there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect recipe for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  UI hiccups&lt;/li&gt;
&lt;li&gt;  mysterious delays&lt;/li&gt;
&lt;li&gt;  hard‑to‑reproduce bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The kind that only appear &lt;strong&gt;in production&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Simple Rule I Follow
&lt;/h1&gt;

&lt;p&gt;Whenever I see:&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="kt"&gt;Task&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who owns this task?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And whenever I see:&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;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Something&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this truly UI state, or just convenient isolation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those two questions alone catch &lt;strong&gt;a surprising number of concurrency&lt;br&gt;
bugs&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Structured concurrency gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  predictable task lifetimes&lt;/li&gt;
&lt;li&gt;  automatic cancellation&lt;/li&gt;
&lt;li&gt;  safe error propagation&lt;/li&gt;
&lt;li&gt;  clear ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unstructured concurrency gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  invisible background work&lt;/li&gt;
&lt;li&gt;  duplicated tasks&lt;/li&gt;
&lt;li&gt;  uncancellable operations&lt;/li&gt;
&lt;li&gt;  unpredictable behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And careless &lt;code&gt;@MainActor&lt;/code&gt; usage can quietly turn the &lt;strong&gt;UI thread into a&lt;br&gt;
bottleneck&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Or as I like to think about it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Structured concurrency is like a well‑run orchestra. &lt;br&gt;
Unstructured concurrency is everyone playing instruments whenever they&lt;br&gt;
feel like it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes it still sounds okay.&lt;/p&gt;

&lt;p&gt;Until it suddenly doesn't.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>ios</category>
      <category>mobile</category>
      <category>swift</category>
    </item>
    <item>
      <title>Memory Leaks Are Architecture Problems</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Mon, 05 Jan 2026 09:09:45 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/memory-leaks-are-architecture-problems-emh</link>
      <guid>https://dev.to/vnayak_hejib/memory-leaks-are-architecture-problems-emh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; Majority iOS Engineers  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written for engineers who debug systems not syntax.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; Large, modular, long-lived production apps&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fliqbcz196ibsr2m1wyg5.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fliqbcz196ibsr2m1wyg5.jpeg" alt=" " width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comfortable Lie We All Learned
&lt;/h2&gt;

&lt;p&gt;(Why &lt;code&gt;[weak self]&lt;/code&gt; Is Not the Fix You Think It Is (UIKit + SwiftUI))&lt;/p&gt;

&lt;p&gt;For years, most of us, iOS developers have been told:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If you have a memory leak, just add &lt;code&gt;[weak self]&lt;/code&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It sounds reasonable.&lt;br&gt;&lt;br&gt;
It sounds technical.&lt;br&gt;&lt;br&gt;
And it’s fundamentally incomplete.&lt;/p&gt;

&lt;p&gt;In real-world iOS applications especially those with multiple teams, modules, coordinators, and long-lived services, &lt;strong&gt;memory leaks are rarely caused by missing &lt;code&gt;weak&lt;/code&gt; keywords&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They are caused by &lt;strong&gt;architecture&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Core Truth
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Memory leaks are not language problems.&lt;br&gt;&lt;br&gt;
They are ownership problems.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swift is memory-safe. ARC works.&lt;br&gt;
What fails is our understanding of &lt;strong&gt;who owns what and for how long&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once an app grows beyond a few screens, memory management stops being about syntax and becomes a &lt;strong&gt;systems design concern&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The &lt;code&gt;[weak self]&lt;/code&gt; Myth
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5bc8suhly9jw0smxkfn.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5bc8suhly9jw0smxkfn.jpeg" alt=" " width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most teams follow this pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;App starts leaking&lt;/li&gt;
&lt;li&gt;Instruments shows retained objects&lt;/li&gt;
&lt;li&gt;Someone adds &lt;code&gt;[weak self]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Leak “goes away”&lt;/li&gt;
&lt;li&gt;Six weeks (or may be 2-3 sprints) later, it’s back!!!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;[weak self]&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; define ownership.&lt;br&gt;&lt;br&gt;
It only avoids &lt;strong&gt;one specific retain cycle&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;[weak self]&lt;/code&gt; hides the symptom, it does not fix the cause.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your architecture is wrong, the leak will resurface elsewhere.&lt;/p&gt;


&lt;h2&gt;
  
  
  Real-World Leak #1: ViewModels Without a Lifetime / ViewModel as a God Object
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0cepfr2iuy3fjnr1kbe.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0cepfr2iuy3fjnr1kbe.jpeg" alt=" " width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most common production leak.&lt;/p&gt;
&lt;h3&gt;
  
  
  How it starts
&lt;/h3&gt;

&lt;p&gt;A ViewModel is created to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hold state&lt;/li&gt;
&lt;li&gt;Handle user actions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How it grows
&lt;/h3&gt;

&lt;p&gt;Soon it owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network calls&lt;/li&gt;
&lt;li&gt;Navigation&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Feature flags&lt;/li&gt;
&lt;li&gt;Deep links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ViewController → ViewModel → Services → Closures → ViewController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The ViewModel has &lt;strong&gt;no clearly defined lifetime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Should it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should it die with the screen?&lt;/li&gt;
&lt;li&gt;Should it live across navigation?&lt;/li&gt;
&lt;li&gt;Be shared across flows?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the answer is unclear, ARC cannot help you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learning:  When an object owns “everything”, nothing can release it safely.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Real-World Leak #2: Long-Lived Objects Owning Short-Lived Ones
&lt;/h2&gt;

&lt;p&gt;This is subtle and extremely destructive.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9hwy6ddu9b8u1p7ba6e.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9hwy6ddu9b8u1p7ba6e.jpeg" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Common examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AppCoordinator&lt;/code&gt; retaining screen callbacks&lt;/li&gt;
&lt;li&gt;Singleton managers holding ViewModels&lt;/li&gt;
&lt;li&gt;Global caches storing closures/callbacks
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;AnalyticsManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleResult&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;Even if &lt;code&gt;viewModel&lt;/code&gt; is weak, the &lt;strong&gt;manager’s lifetime outlives the screen&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A long-lived object must never own a short-lived one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If it does, leaks are guaranteed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learning:  A long-lived object must never own a short-lived one, directly or indirectly.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Combine: When Ownership Is Ignored
&lt;/h2&gt;

&lt;p&gt;I would say, Combine didn’t introduce leaks.&lt;br&gt;
It exposed architectural mistakes(or may be it just amplified bad architecture)!!!&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyqi9ddqs5glgyrtn8vk.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyqi9ddqs5glgyrtn8vk.jpeg" alt=" " width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the culprit&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="n"&gt;publisher&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cancellables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leak depends on &lt;strong&gt;where &lt;code&gt;cancellables&lt;/code&gt; lives&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ViewController → usually safe
&lt;/li&gt;
&lt;li&gt;ViewModel → depends on lifetime
&lt;/li&gt;
&lt;li&gt;Singleton / Coordinator → leak&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Learning: Combine pipelines must obey the same lifetime rules as views or they leak quietly.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;NotificationCenter&lt;/code&gt; Is Not the Villain
&lt;/h2&gt;

&lt;p&gt;NotificationCenter leaks are usually &lt;strong&gt;symptoms&lt;/strong&gt;, not causes.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;deinit&lt;/code&gt; isn’t called, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is retaining this object?&lt;/li&gt;
&lt;li&gt;Why does it live longer than expected?&lt;/li&gt;
&lt;li&gt;⚠️ &lt;code&gt;NotificationCenter&lt;/code&gt; is rarely the root cause, it’s the first visible symptom of bad ownership.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Timers &amp;amp; DisplayLinks: Perfect Leak Multipliers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduledTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;withTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;repeats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&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;Even with &lt;code&gt;[weak self]&lt;/code&gt;, this leaks if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The timer isn’t invalidated&lt;/li&gt;
&lt;li&gt;The run loop outlives the screen&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Learning: Repeating resources must be explicitly tied to lifecycle; architecture decides that, not syntax.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  SwiftUI: Memory Leaks Hidden Behind Simplicity
&lt;/h1&gt;

&lt;p&gt;SwiftUI didn’t remove memory management.&lt;br&gt;&lt;br&gt;
It &lt;strong&gt;made it implicit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That makes ownership mistakes easier and harder to detect.&lt;/p&gt;




&lt;h2&gt;
  
  
  SwiftUI Leak Pattern #1: &lt;code&gt;@StateObject&lt;/code&gt; in the Wrong Place
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ScreenView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@StateObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ScreenViewModel&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;If &lt;code&gt;ScreenView&lt;/code&gt; is recreated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ViewModel may survive longer than expected&lt;/li&gt;
&lt;li&gt;Or be recreated repeatedly&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don’t control &lt;strong&gt;where&lt;/strong&gt; an object is created,&lt;br&gt;
you don’t control &lt;strong&gt;when&lt;/strong&gt; it dies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;@StateObject doesn’t manage memory, it declares ownership.&lt;br&gt;
And ownership must match lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  SwiftUI Leak Pattern #2: EnvironmentObjects With No Exit
&lt;/h2&gt;

&lt;p&gt;EnvironmentObjects are effectively &lt;strong&gt;global ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hold services&lt;/li&gt;
&lt;li&gt;Retain closures&lt;/li&gt;
&lt;li&gt;Reference UI state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They will live far longer than the UI expects.&lt;/p&gt;

&lt;p&gt;Environment ≠ lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  SwiftUI Leak Pattern #3: View Identity Mistakes
&lt;/h2&gt;

&lt;p&gt;Incorrect &lt;code&gt;id&lt;/code&gt; usage causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View recreation&lt;/li&gt;
&lt;li&gt;Object graph duplication&lt;/li&gt;
&lt;li&gt;State retention bugs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;RowView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&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;If identity is unstable, memory behavior is unpredictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  SwiftUI Leak Pattern #4: Navigation Retains State
&lt;/h2&gt;

&lt;p&gt;Navigation stacks can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retain views&lt;/li&gt;
&lt;li&gt;Retain ViewModels&lt;/li&gt;
&lt;li&gt;Retain closures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even when UI disappears.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Navigation is ownership.&lt;br&gt;
Treat it as such.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Architecture Smells That Predict Leaks
&lt;/h2&gt;

&lt;p&gt;If you see these, leaks are inevitable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ViewModels doing navigation&lt;/li&gt;
&lt;li&gt;Singletons coordinating flows&lt;/li&gt;
&lt;li&gt;Managers with no lifecycle&lt;/li&gt;
&lt;li&gt;Extensions as dumping grounds&lt;/li&gt;
&lt;li&gt;“Convenience” APIs growing endlessly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not style issues.&lt;br&gt;
They are &lt;strong&gt;runtime memory failures waiting to happen&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How "Good" Engineers Debug Leaks
&lt;/h2&gt;

&lt;p&gt;Not by guessing!!!&lt;/p&gt;

&lt;h3&gt;
  
  
  Actual workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Instruments → Allocations&lt;/li&gt;
&lt;li&gt;Memory Graph Debugger&lt;/li&gt;
&lt;li&gt;Identify retention chains&lt;/li&gt;
&lt;li&gt;Ask one question:&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Should this object still be alive?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, the architecture is wrong.&lt;/p&gt;

&lt;p&gt;You don’t hunt leaks.&lt;br&gt;
You &lt;strong&gt;prove ownership is invalid&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architectural Fix(Not a tiplist)
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcozyx4npl10omci45pmd.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcozyx4npl10omci45pmd.jpeg" alt=" " width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Strong apps have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicit lifetimes&lt;/li&gt;
&lt;li&gt;Directional ownership&lt;/li&gt;
&lt;li&gt;Short-lived objects owned by short-lived parents&lt;/li&gt;
&lt;li&gt;Long-lived objects that never retain UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This applies equally to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UIKit&lt;/li&gt;
&lt;li&gt;SwiftUI&lt;/li&gt;
&lt;li&gt;Combine&lt;/li&gt;
&lt;li&gt;Coordinators&lt;/li&gt;
&lt;li&gt;Dependency graphs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Truth
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Memory leaks are not Swift problems.&lt;br&gt;&lt;br&gt;
They are architectural lies revealed by runtime.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fix ownership.&lt;br&gt;&lt;br&gt;
Define lifetimes.&lt;br&gt;&lt;br&gt;
Leaks disappear naturally.&lt;/p&gt;




</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Memory Leaks in SwiftUI(Real-World Examples)</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Thu, 20 Nov 2025 09:46:27 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/memory-leaks-in-swiftui-where-they-hide-how-to-catch-them-real-world-examples-84i</link>
      <guid>https://dev.to/vnayak_hejib/memory-leaks-in-swiftui-where-they-hide-how-to-catch-them-real-world-examples-84i</guid>
      <description>&lt;p&gt;Memory Leaks in SwiftUI - Where They Hide &amp;amp; How to Catch Them!!&lt;/p&gt;

&lt;p&gt;SwiftUI makes UI feel effortles, some SwiftUI views exit the screen. but others cling to RAM like it’s the last samosa 😉&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q4n2z9ldq75ezvkvqje.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q4n2z9ldq75ezvkvqje.jpeg" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you ever observed something highly interactive customization flow—started behaving oddly? Nothing crashes, Nothing obviously lags, but each time we navigate through the flow, the app feels &lt;em&gt;heavier&lt;/em&gt;. Not slow… just “sticky,” like the UI was dragging unseen baggage behind it.&lt;/p&gt;

&lt;p&gt;I has a similar situation and after 10–15 navigations, Instruments confirmed my suspicion:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;A whole family of views, view models, timers, and async tasks were still alive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not one or two.&lt;br&gt;&lt;br&gt;
All of them.&lt;/p&gt;

&lt;p&gt;It wasn’t a Combine pipeline gone rogue.&lt;br&gt;&lt;br&gt;
It wasn’t AVPlayer.&lt;br&gt;&lt;br&gt;
It wasn’t some UIKit weirdness.&lt;/p&gt;

&lt;p&gt;It was a &lt;strong&gt;single misused &lt;code&gt;@StateObject&lt;/code&gt; buried inside a child view&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Real Leak That Triggered the Investigation
&lt;/h2&gt;

&lt;p&gt;Inside the main customization screen:&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;@StateObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;BigViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect.&lt;/p&gt;

&lt;p&gt;But deep within a child view:&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;@StateObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;BigViewModel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&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;@ObservedObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;BigViewModel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tiny slip created a &lt;strong&gt;permanent retention cycle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every navigation pushed a new &lt;code&gt;BigViewModel&lt;/code&gt; into memory.&lt;br&gt;&lt;br&gt;
None ever left.&lt;br&gt;
The leak was silent until the user repeated the flow enough times for the app to feel heavier.&lt;/p&gt;

&lt;p&gt;Real world.  Hard to catch.  Easy to create.&lt;/p&gt;


&lt;h2&gt;
  
  
  Where SwiftUI Actually Hides Leaks (Real Examples from Real Apps)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1. Wrong property wrapper (&lt;code&gt;@StateObject&lt;/code&gt; vs &lt;code&gt;@ObservedObject&lt;/code&gt;)&lt;/strong&gt;
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxz98o6oiy8h308f45hu.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxz98o6oiy8h308f45hu.jpeg" alt=" " width="800" height="749"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the #1 cause I’ve seen in large SwiftUI codebases.&lt;/p&gt;

&lt;p&gt;SwiftUI assumes &lt;code&gt;@StateObject&lt;/code&gt; owns the lifecycle.&lt;br&gt;&lt;br&gt;
Child views often live longer than expected (transitions, animations, NavigationStack caching).&lt;/p&gt;
&lt;h2&gt;
  
  
  One wrong wrapper → immortal view model.
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;2. Async Tasks that silently retain &lt;code&gt;self&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3g2m64hpy0bccjbstdb.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3g2m64hpy0bccjbstdb.jpeg" alt=" " width="800" height="759"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran into a bug where a discount recalculation fired on every color change:&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="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;selectedColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recomputeDiscount&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;Harmless-looking, but &lt;code&gt;Task&lt;/code&gt; &lt;strong&gt;captures strongly by default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The recalculation job kept the entire view model—and its whole dependency graph—alive.&lt;/p&gt;

&lt;p&gt;The fix:&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="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recomputeDiscount&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;h3&gt;
  
  
  &lt;strong&gt;3. Timers that were never cancelled&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A carousel featured an auto-scroll timer:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuhea6hmxz2h2p6mex10.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuhea6hmxz2h2p6mex10.jpeg" alt=" " width="800" height="772"&gt;&lt;/a&gt;&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="kt"&gt;Timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;every&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autoconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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;Nobody cancelled it.&lt;/p&gt;

&lt;p&gt;Every “dead” carousel screen stayed… well… very much alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  You don’t notice until you have 20 of them.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. NavigationPath captured in a ViewModel&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This one surprised even senior devs:&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;class&lt;/span&gt; &lt;span class="kt"&gt;NavVM&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NavigationPath&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The minute you capture the entire &lt;code&gt;NavigationPath&lt;/code&gt;, SwiftUI may never free the views, because the path retains every element in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you must observe navigation, observe tokens / IDs, never the full path object.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. EnvironmentObjects retaining everything&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Shared state is great—until it becomes a black hole.&lt;/p&gt;

&lt;p&gt;A global theme object held references to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature view models
&lt;/li&gt;
&lt;li&gt;child store objects
&lt;/li&gt;
&lt;li&gt;even closures that referenced views indirectly
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EnvironmentObjects should be &lt;strong&gt;pure app state&lt;/strong&gt;, not dependency containers.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Catch Leaks Today (and Sleep Better)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Add &lt;code&gt;deinit&lt;/code&gt; logs to every ViewModel&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;deinit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deinit: BigViewModel"&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;If you don’t see this when popping a screen → it’s leaking.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Make &lt;code&gt;[weak self]&lt;/code&gt; a reflex&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Tasks, closures, timers, Combine pipelines — treat them all as suspects.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Audit property wrapper changes during code reviews&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Catch wrong wrappers early.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Stress-test navigation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Push/pull a flow 20–30 times.&lt;br&gt;&lt;br&gt;
Leaks reveal themselves through repetition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8296kggog2n4u5b6bm4.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8296kggog2n4u5b6bm4.jpeg" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SwiftUI’s power comes from its declarative nature.&lt;br&gt;&lt;br&gt;
But the lifecycle behind it is not always intuitive—and sometimes, a single misplaced wrapper or async closure can keep entire view trees alive.&lt;/p&gt;

&lt;p&gt;Once you know where leaks hide, you’ll start seeing them every week.&lt;/p&gt;

&lt;p&gt;And trust me:&lt;br&gt;&lt;br&gt;
Crushing a stubborn leak feels ridiculously satisfying.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>performance</category>
      <category>swiftui</category>
    </item>
    <item>
      <title>The Essentials of Unit Testing in iOS - A Quick Guide</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Tue, 09 Sep 2025 15:19:26 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/the-essentials-of-unit-testing-in-ios-a-quick-guide-3031</link>
      <guid>https://dev.to/vnayak_hejib/the-essentials-of-unit-testing-in-ios-a-quick-guide-3031</guid>
      <description>&lt;p&gt;Unit testing is the &lt;strong&gt;backbone of robust, maintainable iOS apps&lt;/strong&gt;. Think of it like having a safety net under a tightrope, it won’t stop you from writing risky code, but it will catch you if things go wrong. Writing reliable tests ensures your code behaves as expected, prevents regressions, and gives developers confidence when refactoring (without that dreaded “did I just break production?” moment).&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojey6u6j1avn7wke8l3l.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojey6u6j1avn7wke8l3l.jpeg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, we’ll take a &lt;strong&gt;practical (and slightly fun) tour of unit testing in iOS&lt;/strong&gt;. We’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TDD (Test-Driven Development)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection&lt;/strong&gt; for testability
&lt;/li&gt;
&lt;li&gt;The world of &lt;strong&gt;Test Doubles&lt;/strong&gt; : Dummy, Stub, Fake, Mock, Spy (yes, they sound like a crime thriller cast)
&lt;/li&gt;
&lt;li&gt;Testing strategies for &lt;strong&gt;legacy codebases&lt;/strong&gt; with missing test coverage
&lt;/li&gt;
&lt;li&gt;Practical &lt;strong&gt;testing standards and best practices&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And to make it concrete, we’ll walk through everything with a &lt;strong&gt;Login flow example&lt;/strong&gt;, because if there’s one thing every app has, it’s a login screen (and if it fails, your users won’t stick around).&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why Unit Testing Matters in iOS
&lt;/h2&gt;

&lt;p&gt;Unit tests verify individual components of your app in isolation. In iOS, this could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;ViewModel&lt;/strong&gt; in SwiftUI or UIKit (MVVM)
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;network layer&lt;/strong&gt; that authenticates users
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;utility&lt;/strong&gt; or business logic class
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect regressions early
&lt;/li&gt;
&lt;li&gt;Make refactoring safer
&lt;/li&gt;
&lt;li&gt;Enable Test-Driven Development (TDD)
&lt;/li&gt;
&lt;li&gt;Document expected behavior
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By testing individual units, you ensure your app behaves as expected even as the codebase grows or changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Test-Driven Development (TDD)
&lt;/h2&gt;

&lt;p&gt;TDD is a disciplined approach to building software:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a &lt;strong&gt;failing test&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Write minimal code to make it &lt;strong&gt;pass&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor&lt;/strong&gt; for readability and efficiency
&lt;/li&gt;
&lt;/ol&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x2b9yh6ydwwad604jaz.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x2b9yh6ydwwad604jaz.jpeg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LoginViewModel Example:&lt;/strong&gt;&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;struct&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;token&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="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;LoginViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt;
    &lt;span class="kd"&gt;private(set)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;loggedInUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authService&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;loggedInUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&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 setup allows TDD to guide your design while keeping the code testable and maintainable.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Test Doubles: Dummy, Stub, Fake, Mock, Spy
&lt;/h2&gt;

&lt;p&gt;Test doubles allow you to &lt;strong&gt;isolate the unit under test&lt;/strong&gt; from dependencies. Each type has a specific role:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvi0pkdheuyet3hdhivc.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvi0pkdheuyet3hdhivc.jpeg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example in iOS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dummy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Passed but never used&lt;/td&gt;
&lt;td&gt;Placeholder AuthService to satisfy method signature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Returns fixed responses&lt;/td&gt;
&lt;td&gt;AuthServiceStub returning predetermined login results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fake&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lightweight working implementation&lt;/td&gt;
&lt;td&gt;In-memory login service for testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Records method calls, allows verification&lt;/td&gt;
&lt;td&gt;Verify that login() is called exactly once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Records calls and behavior&lt;/td&gt;
&lt;td&gt;Ensure delegate methods or callbacks are triggered&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Examples with Login Flow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dummy&lt;/strong&gt; – used just to satisfy dependency injection, not actually used by the test.&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;class&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceDummy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="c1"&gt;// Not used in this test&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Example: Passing this to LoginViewModel ensures the constructor compiles, &lt;/span&gt;
&lt;span class="c1"&gt;// even though we won’t call login().&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stub&lt;/strong&gt; – provides predefined outputs so tests can run deterministically.&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;class&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceStub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"1234"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Example: Ensures LoginViewModel always gets a known response for given inputs.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fake&lt;/strong&gt; – a simplified but working implementation (e.g., in-memory user store).&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;class&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceFake&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&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="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="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1234"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;storedPassword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;storedPassword&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fakeToken123"&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="c1"&gt;// Example: Acts like a real auth service without network calls.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mock&lt;/strong&gt; – verifies that specific methods are invoked.&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;class&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceMock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&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;loginCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;loginCalled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Example: Lets us check if LoginViewModel actually triggered login().&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Spy&lt;/strong&gt; – tracks how many times or with what data a method is called.&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;class&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceSpy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private(set)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;loginCallCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;loginCallCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"spyToken"&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="c1"&gt;// Example: Useful for verifying multiple login attempts are handled.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Dependency Injection (DI)
&lt;/h2&gt;

&lt;p&gt;Hard-coded dependencies make testing difficult. &lt;strong&gt;DI&lt;/strong&gt; allows injecting stubs, fakes, or mocks:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F481r6d3sz3ygxo66fare.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F481r6d3sz3ygxo66fare.jpeg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;stubService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AuthServiceStub&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LoginViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stubService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes your tests &lt;strong&gt;deterministic and independent&lt;/strong&gt; of network or database services.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Legacy Code and Incremental Testing
&lt;/h2&gt;

&lt;p&gt;Testing legacy apps requires careful planning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify &lt;strong&gt;critical flows&lt;/strong&gt; (e.g., login success/failure)
&lt;/li&gt;
&lt;li&gt;Introduce &lt;strong&gt;protocols and DI&lt;/strong&gt; to decouple code
&lt;/li&gt;
&lt;li&gt;Write &lt;strong&gt;unit tests incrementally&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;test doubles&lt;/strong&gt; to isolate untestable parts
&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;core logic&lt;/strong&gt; over 100% coverage initially
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Refactoring Example:&lt;/strong&gt;&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;protocol&lt;/span&gt; &lt;span class="kt"&gt;AuthFetcherProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;LegacyLoginManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthFetcherProtocol&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthFetcherProtocol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetcher&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fetcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&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="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AuthFetcherStub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AuthFetcherProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&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="nv"&gt;password&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;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"legacyToken"&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;Test:&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;func&lt;/span&gt; &lt;span class="nf"&gt;testLegacyManagerLogin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;stub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AuthFetcherStub&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LegacyLoginManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;XCTAssertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"any"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"any"&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"legacyToken"&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;h2&gt;
  
  
  6. Testing Standards and Best Practices
&lt;/h2&gt;

&lt;p&gt;For effective unit testing in iOS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AAA Pattern (Arrange-Act-Assert):&lt;/strong&gt; Structure tests clearly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep tests independent:&lt;/strong&gt; No shared state between tests.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name tests descriptively:&lt;/strong&gt; &lt;code&gt;testLoginSucceedsWithValidCredentials&lt;/code&gt; &amp;gt; &lt;code&gt;testLogin1&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test behavior, not implementation:&lt;/strong&gt; Verify outcomes, not internal details.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use coverage as a guide, not a goal:&lt;/strong&gt; Focus on meaningful tests.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast feedback:&lt;/strong&gt; Unit tests should run in seconds.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Efficient Testing Strategies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with small units:&lt;/strong&gt; ViewModels, services, utilities
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use DI and test doubles&lt;/strong&gt; to avoid network/db dependencies
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate functional vs. unit tests:&lt;/strong&gt; Logic vs. UI interactions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage XCTest features:&lt;/strong&gt; &lt;code&gt;XCTAssert&lt;/code&gt;, &lt;code&gt;XCTestExpectation&lt;/code&gt;, &lt;code&gt;measure&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incrementally cover legacy code:&lt;/strong&gt; Test core flows first
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unit testing ensures &lt;strong&gt;maintainable, bug-free apps&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;TDD, DI, and test doubles make testing &lt;strong&gt;fast and isolated&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Dummy, Stub, Fake, Mock, and Spy&lt;/strong&gt; for different testing scenarios
&lt;/li&gt;
&lt;li&gt;Legacy code can be incrementally tested and refactored
&lt;/li&gt;
&lt;li&gt;Follow &lt;strong&gt;standards and best practices&lt;/strong&gt; for clarity and reliability
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ios</category>
      <category>testing</category>
      <category>swift</category>
    </item>
    <item>
      <title>LLMs in Xcode: How AI Will Change iOS Development Forever</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Mon, 01 Sep 2025 09:54:40 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/llms-in-xcode-how-ai-will-change-ios-development-forever-1kgk</link>
      <guid>https://dev.to/vnayak_hejib/llms-in-xcode-how-ai-will-change-ios-development-forever-1kgk</guid>
      <description>&lt;h2&gt;
  
  
  🎯 Artificial Intelligence (AI) isn’t just knocking on the door of iOS development....it’s already in the room!!!
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qj946uk666tpl0q0dl8.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qj946uk666tpl0q0dl8.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Large Language Models (LLMs) becoming more deeply integrated into developer tools, Xcode itself is on the brink of an AI-powered revolution. The shift won’t just make coding faster—it will fundamentally transform how iOS apps are built, tested, and shipped.&lt;/p&gt;

&lt;p&gt;Let’s dive into how LLMs will reshape iOS development, why this change is inevitable, and what it means for developers like us.&lt;/p&gt;




&lt;h2&gt;
  
  
  💻 From Autocomplete to "Auto-Engineer"
&lt;/h2&gt;

&lt;p&gt;For years, Xcode has had autocomplete and code snippets. Useful? Yes. But LLMs take this to another level. Instead of just suggesting the next token, AI can &lt;strong&gt;understand your intent&lt;/strong&gt;. Imagine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing &lt;code&gt;func fetchUsers&lt;/code&gt; and AI scaffolds the entire &lt;code&gt;URLSession&lt;/code&gt; code with error handling and decoding.&lt;/li&gt;
&lt;li&gt;Building a SwiftUI view where AI not only completes your layout but suggests accessibility improvements or performance tweaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is no longer autocomplete, it’s &lt;strong&gt;auto-engineering&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture as Code, Not Afterthought
&lt;/h2&gt;

&lt;p&gt;Most teams struggle with consistency across modules. With LLMs, architecture decisions won’t live only in design docs—they’ll &lt;strong&gt;manifest directly in code&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want MVVM with Combine in one feature and Redux-style in another? AI can enforce architectural boundaries automatically.&lt;/li&gt;
&lt;li&gt;Boilerplate ViewModels, Coordinators, and Dependency Injection setups will no longer consume engineering hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows developers to spend less time reinventing the wheel and more time focusing on &lt;strong&gt;business logic and user experience&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI-Powered Testing: From Fragile to Smart
&lt;/h2&gt;

&lt;p&gt;Unit testing is often treated like a chore. But what if Xcode’s AI assistant could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate unit tests&lt;/strong&gt; automatically for every new function.&lt;/li&gt;
&lt;li&gt;Suggest edge cases you forgot about (like offline mode, invalid tokens, or failed JSON decoding).&lt;/li&gt;
&lt;li&gt;Highlight flaky tests and rewrite them for stability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even better, LLMs could analyze &lt;strong&gt;test coverage gaps&lt;/strong&gt; and propose new tests—something most teams don’t have the time to do rigorously.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging with Context, Not Guesswork
&lt;/h2&gt;

&lt;p&gt;Debugging is often about piecing together logs and stack traces. LLMs can flip this process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You paste a crash log, and AI explains in plain English why it happened.&lt;/li&gt;
&lt;li&gt;Instead of just stopping at the line of code, AI correlates it with recent PRs, API contract changes, or even dependency upgrades.&lt;/li&gt;
&lt;li&gt;AI could even &lt;strong&gt;simulate fixes&lt;/strong&gt; and let you preview the impact before you change anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s not just debugging, that’s &lt;strong&gt;proactive problem solving&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security and Performance as Defaults
&lt;/h2&gt;

&lt;p&gt;Performance and security are usually afterthoughts until a bottleneck or breach appears. With AI in Xcode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI can flag &lt;strong&gt;potential security risks&lt;/strong&gt; like storing tokens in &lt;code&gt;UserDefaults&lt;/code&gt; or weak SSL handling.&lt;/li&gt;
&lt;li&gt;It can recommend &lt;strong&gt;performance optimizations&lt;/strong&gt;—like caching strategies, async handling improvements, or reducing unnecessary SwiftUI view recompositions.&lt;/li&gt;
&lt;li&gt;AI could act as a &lt;strong&gt;real-time reviewer&lt;/strong&gt;, catching issues before they ever make it to PR.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Collaboration Reimagined
&lt;/h2&gt;

&lt;p&gt;Xcode projects often have a steep onboarding curve. Imagine a new developer joining your team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of reading endless Confluence pages, they can &lt;strong&gt;chat with Xcode’s AI assistant&lt;/strong&gt;:
&lt;em&gt;“How does networking work in this project?”&lt;/em&gt;
&lt;em&gt;“Where are payments handled?”&lt;/em&gt;
&lt;em&gt;“What does this feature flag control?”&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI doesn’t just search files—it &lt;strong&gt;understands project architecture&lt;/strong&gt;, speeding up onboarding and reducing reliance on tribal knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Developer’s New Role
&lt;/h2&gt;

&lt;p&gt;Some fear AI will replace developers. But in reality, AI will act more like a &lt;strong&gt;supercharged pair programmer&lt;/strong&gt;. The developer’s role will evolve from typing code to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Defining intent&lt;/strong&gt;: what the app should achieve, not how every line looks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewing AI output&lt;/strong&gt;: ensuring correctness, architecture alignment, and edge-case handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elevating creativity&lt;/strong&gt;: focusing on user experience, design, and innovation instead of boilerplate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of replacing us, LLMs will amplify us.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Road Ahead
&lt;/h2&gt;

&lt;p&gt;It’s not science fiction—GitHub Copilot is already integrated with Xcode, and Apple is quietly investing in its own AI tooling. In the next few years, expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built-in &lt;strong&gt;AI code generation in Xcode&lt;/strong&gt; (beyond Copilot).&lt;/li&gt;
&lt;li&gt;AI-assisted &lt;strong&gt;SwiftUI previews&lt;/strong&gt; that optimize performance automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test-first workflows&lt;/strong&gt; where AI generates tests before you even write the implementation.&lt;/li&gt;
&lt;li&gt;AI &lt;strong&gt;refactor engines&lt;/strong&gt; that can migrate legacy Objective-C code to Swift or convert Combine-based code to async/await seamlessly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prompt Engineering for iOS Developers: Speaking the AI’s Language
&lt;/h2&gt;

&lt;p&gt;While Xcode may soon integrate AI into our workflows, the real productivity boost depends on how well we communicate with LLMs. That’s where &lt;strong&gt;prompt engineering&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Be Specific, Not Vague
&lt;/h3&gt;

&lt;p&gt;Instead of asking:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Fix this bug”  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Try:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Fix the retain cycle in this SwiftUI View that uses @StateObject incorrectly.”  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more context you give, the better the response.&lt;/p&gt;




&lt;h3&gt;
  
  
  Show, Don’t Tell
&lt;/h3&gt;

&lt;p&gt;Always paste the relevant &lt;strong&gt;code snippet&lt;/strong&gt; rather than describing it.&lt;br&gt;&lt;br&gt;
LLMs respond far better to &lt;strong&gt;actual code&lt;/strong&gt; than vague summaries.&lt;/p&gt;




&lt;h3&gt;
  
  
  Set the Role
&lt;/h3&gt;

&lt;p&gt;Start prompts with roles like:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“You are an expert iOS architect”
&lt;/li&gt;
&lt;li&gt;“You are an Apple-style API designer”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This frames the answer in the &lt;strong&gt;style you want&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Chain of Thought with Guidance
&lt;/h3&gt;

&lt;p&gt;Break a request into steps:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“First explain the bug, then suggest fixes, then show corrected code.”  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This leads to more &lt;strong&gt;structured, usable responses&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Iterative Refinement
&lt;/h3&gt;

&lt;p&gt;Don’t settle for the first reply. Instead, ask the AI to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Optimize further”
&lt;/li&gt;
&lt;li&gt;“Make it Swifty”
&lt;/li&gt;
&lt;li&gt;“Reduce complexity”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Iteration sharpens the results.&lt;/p&gt;




&lt;h3&gt;
  
  
  Combine AI + Human Review
&lt;/h3&gt;

&lt;p&gt;AI suggestions should &lt;strong&gt;never be merged blindly&lt;/strong&gt; into production code.&lt;br&gt;&lt;br&gt;
Treat AI as a &lt;strong&gt;pair programmer&lt;/strong&gt; — helpful, fast, but still in need of human review.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The rise of LLMs in Xcode will make iOS development faster, smarter, and more consistent. But the real winners won’t be developers who just “use AI”—they’ll be developers who &lt;strong&gt;guide AI&lt;/strong&gt;. The ones who can blend architectural thinking, product vision, and creativity with AI’s raw coding power.&lt;/p&gt;

&lt;p&gt;The future isn’t about developers vs AI.&lt;br&gt;&lt;br&gt;
It’s about developers &lt;strong&gt;with AI&lt;/strong&gt; and together, we’ll build apps at a scale and quality that was once impossible.&lt;/p&gt;




</description>
      <category>ios</category>
      <category>githubcopilot</category>
      <category>swift</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The Secret Life of @State in SwiftUI: Where Does My Data Actually Live?</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Wed, 13 Aug 2025 10:39:31 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/the-secret-life-of-state-in-swiftui-where-does-my-data-actually-live-3be9</link>
      <guid>https://dev.to/vnayak_hejib/the-secret-life-of-state-in-swiftui-where-does-my-data-actually-live-3be9</guid>
      <description>&lt;p&gt;&lt;em&gt;Understanding the truth behind SwiftUI’s most misunderstood property wrapper.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why This Matters&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Every SwiftUI developer uses &lt;code&gt;@State&lt;/code&gt;. It’s the “magic” that makes your UI update when data changes.  &lt;/p&gt;

&lt;p&gt;But here’s the problem:&lt;br&gt;&lt;br&gt;
Most devs don’t actually know &lt;strong&gt;where that data is stored&lt;/strong&gt;, &lt;strong&gt;how it survives view reloads&lt;/strong&gt;, or &lt;strong&gt;why it sometimes behaves unexpectedly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Misunderstanding &lt;code&gt;@State&lt;/code&gt; leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Views not updating
&lt;/li&gt;
&lt;li&gt;❌ Data unexpectedly resetting
&lt;/li&gt;
&lt;li&gt;❌ Performance hits from unnecessary re-renders
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s lift the hood.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpwuzv5vdlctnizj00jf.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpwuzv5vdlctnizj00jf.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;The Basics — What You Think Happens&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CounterView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;View&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;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;It &lt;em&gt;feels&lt;/em&gt; like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; is just a normal property on the &lt;code&gt;CounterView&lt;/code&gt; struct.
&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;count&lt;/code&gt; changes, SwiftUI magically re-renders the view.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But… that’s &lt;strong&gt;not&lt;/strong&gt; the whole story.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;1. First Misconception — “@State is just a stored property”&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Nope. It’s &lt;strong&gt;not&lt;/strong&gt; stored inside your View struct.  &lt;/p&gt;

&lt;p&gt;SwiftUI views are value types — they get recreated all the time.&lt;br&gt;&lt;br&gt;
If &lt;code&gt;@State&lt;/code&gt; lived directly in your struct, you’d lose its value every time your view re-rendered.  &lt;/p&gt;

&lt;p&gt;Instead, &lt;code&gt;@State&lt;/code&gt; is a property wrapper that stores your data &lt;strong&gt;outside&lt;/strong&gt; the view, in a special SwiftUI-managed heap storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your View struct is the blueprint.
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@State&lt;/code&gt; storage is the hidden container SwiftUI manages.
&lt;/li&gt;
&lt;li&gt;The two are connected by an &lt;strong&gt;ID&lt;/strong&gt; SwiftUI assigns when the view hierarchy is built.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;2. What Really Happens When You Declare &lt;code&gt;@State&lt;/code&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When SwiftUI first creates your view:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It allocates a hidden storage object (a “State box”).&lt;/li&gt;
&lt;li&gt;This object lives for as long as the view identity stays the same in the hierarchy.&lt;/li&gt;
&lt;li&gt;Your view gets a reference to that box through the &lt;code&gt;wrappedValue&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key point:&lt;/strong&gt; View identity.&lt;br&gt;&lt;br&gt;
Change the identity (like swapping one &lt;code&gt;.id&lt;/code&gt; for another in a &lt;code&gt;List&lt;/code&gt;), and the storage disappears — &lt;code&gt;@State&lt;/code&gt; resets to its initial value.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;3. The Lifecycle in Real Time&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s track it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialization&lt;/strong&gt; → SwiftUI allocates storage outside the struct.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutation&lt;/strong&gt; → You update the value, SwiftUI schedules a re-render.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recreation&lt;/strong&gt; → Your struct is replaced, but the &lt;code&gt;@State&lt;/code&gt; box is reattached.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destruction&lt;/strong&gt; → Identity changes → storage is destroyed.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 &lt;em&gt;Real-world pitfall:&lt;/em&gt; if you’re building complex UIs with multiple conditional branches, you can accidentally reset state when toggling between branches.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Reality — SwiftUI’s View Identity&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;SwiftUI views are &lt;strong&gt;structs&lt;/strong&gt;. That means they are &lt;strong&gt;value types&lt;/strong&gt; — new copies get created constantly.  &lt;/p&gt;

&lt;p&gt;So if &lt;code&gt;@State&lt;/code&gt; really lived &lt;em&gt;inside&lt;/em&gt; your view struct, you’d lose your data every time the view refreshed.  &lt;/p&gt;

&lt;p&gt;The trick?&lt;br&gt;&lt;br&gt;
&lt;code&gt;@State&lt;/code&gt; stores your data &lt;strong&gt;outside&lt;/strong&gt; the view — in SwiftUI’s internal storage system — and links it to your view via &lt;strong&gt;identity&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;How Identity Works&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When SwiftUI renders a view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It checks if the “new” view is the same &lt;strong&gt;identity&lt;/strong&gt; as the previous one.
&lt;/li&gt;
&lt;li&gt;If yes, it reuses the same &lt;code&gt;@State&lt;/code&gt; storage.
&lt;/li&gt;
&lt;li&gt;If no, it throws away the old storage and starts fresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Identity is determined by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;View type&lt;/li&gt;
&lt;li&gt;Position in the view hierarchy&lt;/li&gt;
&lt;li&gt;Any explicit &lt;code&gt;.id(...)&lt;/code&gt; you set&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why It Matters for View Identity&lt;br&gt;
State storage is tied to the identity of the view in the view hierarchy, not the variable name.&lt;br&gt;
If SwiftUI thinks your view is different (e.g., changes id, order in the hierarchy, or parent view type), it will:&lt;/p&gt;
&lt;h2&gt;
  
  
  ** Tear down the old storage **
&lt;/h2&gt;

&lt;p&gt;Create new storage (resetting your @State)&lt;br&gt;
Example:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;MyView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// gets a new @State storage when toggle changes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;MyView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// different identity =&amp;gt; state resets&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix: Give views a stable identity with .id(someStableValue) if you want state to persist.&lt;/p&gt;

&lt;p&gt;That’s why reordering, wrapping in new containers, or changing &lt;code&gt;.id&lt;/code&gt; can reset your &lt;code&gt;@State&lt;/code&gt;.------&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Where Does It Physically Live in Memory?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@State&lt;/code&gt; wrapper conforms to &lt;code&gt;DynamicProperty&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;When compiled, SwiftUI injects a hidden &lt;code&gt;_State&lt;/code&gt; struct that references a &lt;code&gt;StateStorage&lt;/code&gt; object in the heap. That storage is owned and tracked by the SwiftUI runtime.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your View struct → &lt;strong&gt;on the stack&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your &lt;code&gt;@State&lt;/code&gt; data → &lt;strong&gt;on the heap&lt;/strong&gt; (safe from constant view re-creations)&lt;/li&gt;
&lt;/ul&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlp3681bpba79klkvk1e.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlp3681bpba79klkvk1e.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. Real-Time Issues You’ll Actually Hit&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Data Reset on Navigation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;NavigationLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Open Detail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DetailView&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;DetailView&lt;/code&gt; has &lt;code&gt;@State&lt;/code&gt; and you navigate away &amp;amp; back — boom, your data resets.&lt;br&gt;&lt;br&gt;
Why? New view identity each time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use &lt;code&gt;@StateObject&lt;/code&gt; or pass the data from a higher-level view.&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ State Not Updating
&lt;/h3&gt;

&lt;p&gt;If your state depends on something that changes but your view identity stays constant, SwiftUI won’t refresh.&lt;br&gt;&lt;br&gt;
Sometimes you need &lt;code&gt;.id(someValue)&lt;/code&gt; to force identity change.&lt;/p&gt;

&lt;h3&gt;
  
  
  3️⃣ Multiple Copies of State
&lt;/h3&gt;

&lt;p&gt;Put a view with &lt;code&gt;@State&lt;/code&gt; inside a &lt;code&gt;List&lt;/code&gt;? Each row gets its &lt;strong&gt;own&lt;/strong&gt; copy of state, tied to its own identity.&lt;br&gt;&lt;br&gt;
Reordering rows? State might shuffle or reset.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6. How to Think About &lt;code&gt;@State&lt;/code&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If UIKit was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’ll hold onto this reference until you tell me otherwise,”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SwiftUI with &lt;code&gt;@State&lt;/code&gt; is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’ll keep this value alive for as long as your view’s identity stays in the tree — but break that link, and I’m tossing it.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Key Takeaways&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rules of Thumb&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;@State&lt;/code&gt; for simple, view-local, short-lived data
&lt;/li&gt;
&lt;li&gt;If data must survive view re-creations, lift it up or use &lt;code&gt;@StateObject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Be careful with &lt;code&gt;.id(...)&lt;/code&gt; — it can reset your state
&lt;/li&gt;
&lt;li&gt;Remember: position in the view tree matters
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;@State&lt;/code&gt; is not a normal property — it’s SwiftUI’s way of &lt;strong&gt;outsourcing your data to an identity-bound container&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
If your view’s identity changes, your &lt;code&gt;@State&lt;/code&gt; starts fresh.  &lt;/p&gt;

&lt;p&gt;Once you truly understand that, you’ll stop fighting SwiftUI and start making it work for you.&lt;/p&gt;




</description>
      <category>ios</category>
      <category>mobile</category>
      <category>swift</category>
      <category>swiftui</category>
    </item>
    <item>
      <title>What Really Happens When You Call an async Function in Swift?</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Tue, 29 Jul 2025 11:16:11 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/what-really-happens-when-you-call-an-async-function-in-swift-5bo0</link>
      <guid>https://dev.to/vnayak_hejib/what-really-happens-when-you-call-an-async-function-in-swift-5bo0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Calling await in Swift feels like casting a spell — except Swift is the sorcerer and you're just waving a wand.&lt;br&gt;
And like any good spell, there's a secret potion brewing underneath.&lt;br&gt;
We’ve all been there — late-night debugging, coffee in hand, watching your async function freeze and unfreeze like some kind of dark magic!!"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swift’s &lt;code&gt;async/await&lt;/code&gt; model makes asynchronous code elegant, readable, and non-blocking. But if you've ever paused mid-debug and wondered:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;“Wait... where did my function actually &lt;em&gt;go&lt;/em&gt; after I hit &lt;code&gt;await&lt;/code&gt;?”&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
—you’re in the right place.&lt;/p&gt;

&lt;p&gt;This blog post takes you &lt;strong&gt;behind the scenes&lt;/strong&gt; of Swift's async functions, demystifying what really happens when you call them; underneath that calm, elegant surface is a full-blown circus of continuations, task schedulers, and a meticulously crafted state machine — all generated for you by the Swift compiler.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let us consider as an example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you write something like &lt;code&gt;await fetchItems()&lt;/code&gt;, it feels like magic. But behind the curtain, Swift is doing a whole lot of heavy lifting to make &lt;code&gt;async/await&lt;/code&gt; feel seamless — while keeping your app smooth and responsive. Let’s dive into how async functions really work in Swift, what makes them efficient, and why they’re much more than just syntactic sugar over callbacks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvtttxd42x2e9j56udeg.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvtttxd42x2e9j56udeg.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first glance, async functions look like ordinary sequential code:&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To you, the developer, it reads top-to-bottom — just like synchronous code.&lt;/p&gt;

&lt;p&gt;But in reality, every time you hit an &lt;code&gt;await&lt;/code&gt;, Swift pauses the function, hands over control to something else (like the main run loop), and resumes it later when the awaited value becomes available. This is more like a theatrical intermission than a simple pause.&lt;/p&gt;




&lt;h2&gt;
  
  
  Continuations: The Brain 🧠 Behind &lt;code&gt;await&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Swift uses a powerful concept called &lt;strong&gt;continuations&lt;/strong&gt;. Think of a continuation as a &lt;em&gt;"bookmark"&lt;/em&gt; for the current point in a function — it stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function's local variables,&lt;/li&gt;
&lt;li&gt;The point where execution left off,&lt;/li&gt;
&lt;li&gt;And the next instruction to run when the awaited task completes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when you write:&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUserProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swift compiles this into a state machine that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suspend itself,&lt;/li&gt;
&lt;li&gt;Store context,&lt;/li&gt;
&lt;li&gt;And resume from exactly where it left off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In lower-level terms, Swift transforms your async function into something like this:&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;enum&lt;/span&gt; &lt;span class="kt"&gt;FetchState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;waitingForProfile&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This machinery is automatically generated during compilation — so your async function becomes a state machine under the hood, but you don’t have to manage that complexity manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Under the Hood: Swift’s Task System
&lt;/h2&gt;

&lt;p&gt;Behind all of this is Swift’s &lt;strong&gt;structured concurrency model&lt;/strong&gt;, built on top of a cooperative task system.&lt;/p&gt;

&lt;p&gt;When you create a new async task:&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="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadData&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;Swift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allocates memory for a new task object,&lt;/li&gt;
&lt;li&gt;Schedules it for execution on a global executor,&lt;/li&gt;
&lt;li&gt;Executes its body until the first &lt;code&gt;await&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Then suspends it — saving its continuation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Later, when the awaited result is ready, the continuation is resumed on the appropriate executor (often the main actor if you're on the UI thread).&lt;/p&gt;

&lt;p&gt;This model is &lt;strong&gt;non-blocking&lt;/strong&gt;, so multiple async tasks can be scheduled and paused concurrently — all without spawning heavyweight threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Async/await ≠ Threads
&lt;/h2&gt;

&lt;p&gt;This is worth highlighting: &lt;strong&gt;calling an async function does NOT create a new thread&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you call &lt;code&gt;await fetchData()&lt;/code&gt; from the main thread, the function suspends itself without blocking — freeing up the thread to keep UI rendering smooth. When the awaited result is available, Swift resumes the function on the appropriate thread or executor.&lt;/p&gt;

&lt;p&gt;This makes async/await an extremely lightweight and efficient model — far better than the GCD + callbacks spaghetti many iOS developers had to deal with in the past.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Magic of &lt;code&gt;await&lt;/code&gt; in SwiftUI
&lt;/h2&gt;

&lt;p&gt;If you’re using SwiftUI and structured concurrency, all of this allows you to write elegant code like:&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;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;networkService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to the underlying continuation-based machinery, you don’t have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dispatch queues,&lt;/li&gt;
&lt;li&gt;Completion handlers,&lt;/li&gt;
&lt;li&gt;Thread safety.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Swift takes care of it. But knowing how it works under the hood can help you reason about bugs, optimize performance, and understand suspension points better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: Checked Continuations
&lt;/h2&gt;

&lt;p&gt;Sometimes you need to bridge old completion-based APIs with Swift concurrency. That’s where &lt;strong&gt;checked continuations&lt;/strong&gt; come in:&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;func&lt;/span&gt; &lt;span class="nf"&gt;legacyLogin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;withCheckedThrowingContinuation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;legacyAuthService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error&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;This allows you to “manually” capture and resume the continuation — but it also includes &lt;strong&gt;safety checks&lt;/strong&gt; to prevent misuse (e.g., multiple resumes, never-resumed bugs).&lt;/p&gt;




&lt;h2&gt;
  
  
  🧵 Wrapping Up: Async as State Machines
&lt;/h2&gt;

&lt;p&gt;To recap, when you write an async function in Swift:&lt;/p&gt;

&lt;p&gt;✅ It’s compiled into a state machine that can suspend and resume.&lt;br&gt;&lt;br&gt;
✅ Suspension happens via continuations stored in memory.&lt;br&gt;&lt;br&gt;
✅ Async functions don’t block threads — they’re suspended cooperatively.&lt;br&gt;&lt;br&gt;
✅ Swift’s structured concurrency handles lifecycle, error propagation, and cancellation for you.&lt;/p&gt;

&lt;p&gt;Knowing this helps you appreciate the performance, safety, and elegance Swift offers — and gives you the tools to write more thoughtful concurrent code.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiv2rws8v6wz4kway46vd.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiv2rws8v6wz4kway46vd.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The image visualizes key concepts like:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Suspension points as space stations where the function can pause and resume&lt;/li&gt;
&lt;li&gt;The main thread continuing to handle other tasks (like UI updates)&lt;/li&gt;
&lt;li&gt;Task bubbles representing concurrent work happening in the background&lt;/li&gt;
&lt;li&gt;Swift's cooperative nature where async functions play nicely with the system&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Final Thought
&lt;/h2&gt;

&lt;p&gt;Next time you type &lt;code&gt;await&lt;/code&gt;, remember: you’re not pausing the universe — you’re crafting a finely orchestrated performance of mini-intermissions, backstage crew (executors), and set changes (continuations) that bring modern concurrency to life.&lt;/p&gt;

&lt;p&gt;Thanks for reading :)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Art of Dependency Injection in SwiftUI</title>
      <dc:creator>Vinayak G Hejib</dc:creator>
      <pubDate>Mon, 21 Jul 2025 10:46:15 +0000</pubDate>
      <link>https://dev.to/vnayak_hejib/the-art-of-dependency-injection-in-swiftui-3fcd</link>
      <guid>https://dev.to/vnayak_hejib/the-art-of-dependency-injection-in-swiftui-3fcd</guid>
      <description>&lt;h3&gt;
  
  
  How I Stopped Worrying and Learned to Love Passing Stuff Around*
&lt;/h3&gt;

&lt;p&gt;If SwiftUI had a motto, it might be: &lt;em&gt;“Less is more, but good luck injecting that API client.”&lt;/em&gt;&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpyqv6uv1r2x5v4q2cn3.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpyqv6uv1r2x5v4q2cn3.png" alt=" " width="800" height="1474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  In the world of SwiftUI, dependency injection is like dating: you want clarity, low maintenance, and definitely no surprises. Whether you're passing view models, shared state, or static configuration, how you inject those dependencies can make or break your architecture — and your sanity.
&lt;/h4&gt;

&lt;p&gt;In this post, we’ll explore &lt;strong&gt;three elegant ways&lt;/strong&gt; to inject dependencies into SwiftUI views and when to use (or avoid) each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Constructor-based injection
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;@Environment&lt;/code&gt;-based injection with custom keys
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;@EnvironmentObject&lt;/code&gt; for shared observable state
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll use a fun example: a &lt;strong&gt;theme-aware counter screen&lt;/strong&gt;. No analytics, no token managers — just a beautiful button and a splash of color.&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ Constructor-based Injection: The “Polite Guest” Approach
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Color&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Color&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CounterView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Theme&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;View&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;"Count: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&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;h3&gt;
  
  
  When to use it??:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You value &lt;strong&gt;clarity and control&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You love previews and &lt;strong&gt;unit testing without wizardry&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your theme shouldn't mysteriously change mid-scroll&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2️⃣ Environment Injection: &lt;em&gt;The Magical Air You Breathe&lt;/em&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Define a Custom Environment Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ThemeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EnvironmentKey&lt;/span&gt; &lt;span class="p"&gt;{&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;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;EnvironmentValues&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;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ThemeKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ThemeKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newValue&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;h3&gt;
  
  
  Step 2: Inject It Once, Use It Anywhere
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CounterView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;theme&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;View&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;"Count: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&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;h3&gt;
  
  
  Step 3: Set the Theme from a Parent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;RootView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&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;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;CounterView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indigo&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;h3&gt;
  
  
  When to use it??:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your dependency is &lt;strong&gt;lightweight and stable&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You don't want to &lt;strong&gt;manually pass values 10 levels deep&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You love magic that comes with a fallback value&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3️⃣ &lt;code&gt;@EnvironmentObject&lt;/code&gt;: The Loud Roommate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ThemeSettings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CounterView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@EnvironmentObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ThemeSettings&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;View&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;"Count: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Increment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&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;h3&gt;
  
  
  Inject it globally:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MyApp&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="kd"&gt;@StateObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;themeSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ThemeSettings&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;CounterView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;themeSettings&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;h3&gt;
  
  
  When to use it??:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;observable shared state&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You don’t mind &lt;strong&gt;runtime crashes when someone forgets &lt;code&gt;.environmentObject(...)&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You like living on the edge&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚖️ Environment Key vs EnvironmentObject: Explained with Snacks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You have a 🍪 (biscuit)&lt;/th&gt;
&lt;th&gt;Use &lt;code&gt;@Environment&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Use &lt;code&gt;@EnvironmentObject&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The 🍪 never changes and can be safely shared&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The 🍪 might change (someone might take a bite)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want a default fallback 🍪&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You forget to bring a 🍪 and the app crashes&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary: Which Flavor to Choose?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Avoid If...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Constructor Injection&lt;/td&gt;
&lt;td&gt;Explicit, testable setup&lt;/td&gt;
&lt;td&gt;You hate writing initializers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@Environment&lt;/code&gt; + Key&lt;/td&gt;
&lt;td&gt;Global/static dependencies (themes)&lt;/td&gt;
&lt;td&gt;You need reactivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@EnvironmentObject&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared state that updates views&lt;/td&gt;
&lt;td&gt;You need compile-time guarantees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;SwiftUI gives you powerful, expressive tools for managing dependencies — just enough structure to keep your code clean, and just enough magic to feel ✨Swifty✨.&lt;/p&gt;

&lt;p&gt;So the next time you’re passing a &lt;code&gt;Theme&lt;/code&gt;, a &lt;code&gt;Settings&lt;/code&gt; object, or even a metaphorical biscuit 🍪, ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this need to be shared?&lt;/li&gt;
&lt;li&gt;Does it change?&lt;/li&gt;
&lt;li&gt;Do I want control or convenience?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because dependency injection in SwiftUI is like parenting: it’s all about &lt;strong&gt;boundaries, visibility, and who gets to press the increment button.&lt;/strong&gt; 😉&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swiftui</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
