<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://jano.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jano.dev/" rel="alternate" type="text/html" /><updated>2026-02-23T17:35:02+01:00</updated><id>https://jano.dev/feed.xml</id><title type="html">My awesome title</title><subtitle>Programming, swift, iOS, and stuff.</subtitle><entry><title type="html">Explaining code with RAG</title><link href="https://jano.dev/swift/llm/2025/12/11/Explain-Algorithm.html" rel="alternate" type="text/html" title="Explaining code with RAG" /><published>2025-12-11T03:03:03+01:00</published><updated>2025-12-11T03:03:03+01:00</updated><id>https://jano.dev/swift/llm/2025/12/11/Explain-Algorithm</id><content type="html" xml:base="https://jano.dev/swift/llm/2025/12/11/Explain-Algorithm.html"><![CDATA[<p>RAG + call graph expansion + LLM = codebase explanations.</p>

<p>I wrote a <a href="https://apps.apple.com/us/app/semly/id6749020576?mt=12">semantic search tool</a>, then piped the results through an agent to explain algorithms.
 <br />e.g. <i>explain oauth authentication protocol</i></p>

<div class="thumbnail-gallery">
  <a href="#img1"><img src="/images/exp-oauth-1.png" alt="OAuth flow diagram 1" /></a>
  <a href="#img2"><img src="/images/exp-oauth-2.png" alt="OAuth flow diagram 2" /></a>
</div>

<div id="img1" class="lightbox">
  <a href="#" class="lightbox-close">&times;</a>
  <img src="/images/exp-oauth-1.png" alt="OAuth flow diagram 1" />
</div>

<div id="img2" class="lightbox">
  <a href="#" class="lightbox-close">&times;</a>
  <img src="/images/exp-oauth-2.png" alt="OAuth flow diagram 2" />
</div>

<p><small>The answer above was generated solely from a Swift codebase.</small></p>

<p>The tool selects the relevant information using a combination of <a href="/swift/2025/09/28/semantic-search.html">semantic search</a>, heuristics, and the call hierarchy graph. Generating an explanation is generally twice as fast as asking a coding agent, and half as cheap.</p>

<p>This implementation is Swift/Xcode-centric. Semly indexes Swift and relies on Xcode’s <a href="https://github.com/swiftlang/indexstore-db">IndexStore</a> to build a call-graph.</p>

<h2 id="the-problem">The Problem</h2>

<p>I’m trying to answer a user’s question about an algorithm buried in a mid-sized codebase (100–500k lines). The code exceeds the model’s context window, so the challenge is finding the relevant pieces.</p>

<p>An agent approach is to grep synonyms of the query words, evaluate results, then grep further for symbols found until the picture is clear. This is effective—once the agent finds a trail it follows guided by its own intelligence.</p>

<p>In comparison, semantic search is faster and uses fewer tokens. The tradeoff is depth. An agent can continue indefinitely; semantic search gives you a first step. The quality ceiling is lower, but the cost floor is much lower too.</p>

<p>The best results come from combining both: semantic search for initial exploration (it’s an external system, so token-cheap), then let the agent judge whether the answer is sufficient or whether it needs to grep additional details or start over.</p>

<p>From the terminal, run <em>semly explain</em> or <em>outline</em>, and you’ll get a terminal version of the answers above.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ % semly outline "explain oauth authentication protocol" --project OAuth2
 ├─ S1 : Start OAuth Flow LoginViewController.swift:9-145
 ├─ S2 : Build Authorization Request OAuth2Client.swift:6-121
 │  └─ S3 : Exchange Code for Token AccessTokenRequest.swift:2-11
 ├─ S4 : Decode Access Token Response AccessTokenResponse.swift:18-119
 └─ S5 : Load Configuration OAuth2Configuration.swift:50-50
</code></pre></div></div>

<h2 id="why-semantic-search-isnt-enough">Why Semantic Search Isn’t Enough</h2>

<p>Semantic search finds code conceptually similar to the query, but ignores the relevancy of the code structure, and doesn’t follow-up the call hierarchy. Therefore, what’s missing here is to expand the call graph of each result, and pick the most likely graph to answer the question.</p>

<h2 id="building-the-index">Building the Index</h2>

<p>A required first step is to index the codebase. This inconvenience is why agents don’t implement semantic search.</p>

<h3 id="chunking">Chunking</h3>

<p>First, it splits files into <dfn>chunks</dfn>—contiguous slices of text with a file path and line range.</p>

<p>It is critical to split at semantic boundaries. For source code you don’t want half of one function glued to half of the next, otherwise the resulting code is confusing, and the embedding won’t be representative. I used a parser (<a href="https://github.com/swiftlang/swift-syntax">SwiftSyntax</a> for Swift) to identify these boundaries. Each chunk represents a coherent unit of meaning. In practice this involved trial and error and dealing with a lot of cases, Swift is complicated.</p>

<p>Markdown, however, was simple: split by sections, paragraphs, tables, etc.</p>

<h3 id="embedding">Embedding</h3>

<p>The system calculates the embedding for each chunk. It stores each chunk’s text, line range, file path, and embedding in SQLite.</p>

<p>When the user sends a query, the system embeds the query, then looks up similar embeddings in the database. The operation to calculate similarity is called cosine similarity. The result is a list of chunks hopefully related to the user query.</p>

<p>The indexing process interleaves disk I/O with embedding creation, so the CPU is mostly idle. You can leave it running in the background—it scans for changes on a configurable interval (say, 10 minutes) using file modification times, hashes, and file events. Negligible overhead.</p>

<h3 id="symbols-and-the-call-graph">Symbols and the Call Graph</h3>

<p>Chunks capture text. The source code symbols in that text capture structure.</p>

<p>A <dfn>symbol</dfn> is a declaration: a function, method, initializer, computed property, or closure. The indexer extracts these from the syntax tree and stores them with their name, kind, file path, and line span. Closures get a synthetic name like “closure@42”. Importantly, the indexer also extracts call edges—which symbol calls which other symbol.</p>

<p>This gives us a graph. When semantic search finds a relevant function, the tool can ask: what calls this? What does it call? The answer might be more relevant than the original hit.</p>

<p>For cross-file relationships, the system uses <dfn>USR</dfn>s (Unified Symbol Resolution identifiers) from Xcode’s <a href="https://github.com/swiftlang/indexstore-db">IndexStore</a> when available. A USR is a stable identifier for a declaration that works across files and modules. Without it, cross-file callees often remain unresolved.</p>

<p>In order to use the IndexStore, the user must compile the source and then point to its folder within Derived Data. This is done in the application’s UI from the properties of the project. This step exists because some symbols are dynamically resolved and can’t be figured out just by looking at the source. In Swift the compiler always knows what calls what at compilation time, so looking up the store is the only way to get this information. If skipped, the application tries to compensate by favoring locality and <em>lexical overlap</em> (fancy term for string overlap).</p>

<h2 id="the-query-pipeline">The Query Pipeline</h2>

<p>When a question arrives, the ‘explain’ feature runs through five stages:</p>

<ol>
  <li>Retrieval: semantic search for chunks related to the query.</li>
  <li>Symbol mapping: convert chunks to structural units (functions, methods) the system can traverse.</li>
  <li>Graph traversal: walk callers and callees to find code semantic search missed.</li>
  <li>Ranking: score by graph depth, locality, and lexical overlap with the question.</li>
  <li>Context composition: concatenate the top chunks into a prompt the model can use.</li>
</ol>

<h3 id="retrieval">Retrieval</h3>

<p>The system gathers <dfn>seeds</dfn> (initial chunks) from three sources:</p>

<ol>
  <li>Cosine similarity on the database using query embedding as input.</li>
  <li>Full text search on the symbols of the database using query terms.</li>
  <li>Symbol search in the <a href="https://github.com/swiftlang/indexstore-db">IndexStore</a> if available.</li>
</ol>

<p>Step 1 returns embeddings, step 2 and 3 return symbols. The database stores triplets of embeddings, symbols, and chunks, so having one lets you query the others. Then perform an additional step of common sense heuristics: discriminate against test files, CLI boilerplate, and UI scaffolding.</p>

<p>For markdown, the indexer treats section headers as symbols. I didn’t implement PDF because it is not semantically annotated and would require clunky heuristics based on font size, styles throughout the document, and common terms.</p>

<p>Option <em>Enable lexical prefilter</em>. When enabled it discards symbols that lack a string overlap with the query. This is useful if your query mentions a specific symbol you are looking for. Default is off because it lets you type vague queries, which is the most common case.</p>

<h3 id="graph-traversal">Graph Traversal</h3>

<p>Embeddings, symbols, and chunks are all linked in the database. For instance, a chunk covering lines 50–80 maps to whatever function or method is declared in that range.</p>

<p>Therefore the system can focus on the symbols to build a graph and then traverse it. For source code this means moving in two directions: callers (who uses this?) and callees (what does this use?). In Semly the walk is bounded—depth ≤3, nodes ≤10—to keep results focused rather than sprawling across the entire codebase.</p>

<p>This is where the traversal discovers code that semantic search missed. A utility function might have a generic name and boring documentation, but if it’s called by three of the seed symbols, it’s probably relevant.</p>

<h3 id="ranking">Ranking</h3>

<p>The expanded set of symbols needs ranking. The ranker combines several signals: graph depth (closer to seeds is better), locality (same file or module), lexical overlap with the question, and embedding similarity.</p>

<p>The system also expands locally: if the graph is thin, it adds neighboring chunks from the same file—the function above and below a hit. This helps when the relevant code is clustered.</p>

<p>The ranking of symbols whose string overlaps with the query is increased. e.g. you asked about a pipeline and there is a class name containing that word.</p>

<p>Option <em>Anchor-only downweight scale</em>. Signatures (or headers) are keyword-dense so they are too boosted by lexical overlap, full text search and cosine similarity. This feature only kicks in when results are signatures only to resurface the bodies. Most times signatures are indexed with their bodies and this is not used.</p>

<h3 id="context-composition">Context Composition</h3>

<p>The top-ranked symbols map back to chunks. The pipeline concatenates these into a single text block with file paths and line hints, trimmed to fit the model’s token budget. This is what the model sees: the question and a curated selection of code. The graph itself is never sent to the model. It’s scaffolding for selection, not part of the prompt.</p>

<p>I’m using a budget of 24k tokens (~1,500 lines of code). How much you should use depends on the quality and number of the chunks retrieved. Since I’m using GPT-5 mini (400k context tokens) you can go to Settings and increase it.</p>

<h2 id="generating-the-answer">Generating the Answer</h2>

<p>With context composed, the system calls the model. The prompt includes the question and the selected chunks. The model generates prose, and because each chunk carries its file path and line range, it can include links that direct the user to the relevant code.</p>

<p>So the full loop is: question → semantic retrieval → structural expansion → ranked selection → grounded answer.</p>

<h2 id="tuning-the-pipeline">Tuning the Pipeline</h2>

<p>I experimented with several options in the form of flags. They are not exposed because they represent the best configuration. I’m showing them here to offer some insight on the inner workings.</p>

<p>Sample query</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>semly explain "How does the per-file pipeline process a file: chunking → embeddings → transactional DB sync?" 
--project Semly 
--limit 10 
--topK 3 
--indexstore-seeding 
--oversize-anchors 
--include-dependencies 
--outline
</code></pre></div></div>

<ul>
  <li><strong>–limit N</strong> caps the number of <dfn>anchors</dfn> (final chunks sent to the model) and the total context size. Lower values produce tighter, more focused answers. Higher values help for broad architectural questions.</li>
  <li><strong>–topK K</strong> controls neighbor expansion—how many same-file chunks to add around each anchor when the graph is sparse.</li>
  <li><strong>–oversize-anchors</strong> allows large spans when the important logic sits in a single big function. By default, the system caps chunk size to maintain diversity.</li>
  <li><strong>–include-dependencies</strong> includes code from packages and dependencies, which are normally filtered to keep focus on the main project.</li>
  <li><strong>–indexstore-seeding</strong> enables USR-based seeding and edge repair using the configured Xcode IndexStore. This improves cross-file precision but requires a built project with an up-to-date index.</li>
</ul>

<p>Beyond flags, I’ve made the selection step more resilient: when the graph is sparse or noisy, <em>explain</em> can fall back to using whole files and focus only on code that’s connected in the call graph. It also drops obvious noise (tests, UI, CLI scaffolding) so the model sees real logic instead of boilerplate. In practice this yields richer context for complex questions without overwhelming the model with irrelevant text.</p>

<p>Outline runs the same retrieval pipeline but asks the model for strict JSON; it takes ~230% longer. Isn’t it wild that 25 years ago we had hierarchy calls for Java but today we need to stitch one together using the IndexStore?</p>

<h2 id="is-this-any-good">Is this any good?</h2>

<p>Locating code is definitely faster during an initial exploration. Claims of “using product X reduces Claude token consumption by 80%” are a cherry pick of this first step.</p>

<p>Explaining code misses more than an agent would. It does better if IndexStore is connected because that provides a true hierarchy of calls, rather than attempting to infer it. I prompted the agent to evaluate its own confidence on the answer and avoid hallucinations.</p>

<p>But it saves tokens. Your main agent gets a more or less accurate version of the algorithm and can continue from there. This is what Claude does already when it uses Haiku to search code.</p>

<p>Note this <em>explain</em> feature presented here is not like <a href="https://codeium.mintlify.app/windsurf/codemaps">Windsurf codemaps</a>. What they do, it seems, is taking part of the hierarchy call graph and requesting a shallow explanation.</p>

<p>Overall, this experiment is a negligible optimization. “You can just ask things,” and live a life without complications. The difference is for initial exploration roughly ~15 seconds and 2% percent on the token context.</p>]]></content><author><name></name></author><category term="swift" /><category term="LLM" /><summary type="html"><![CDATA[RAG + call graph expansion + LLM = codebase explanations.]]></summary></entry><entry><title type="html">Embedding with MiniLM</title><link href="https://jano.dev/swift/2025/09/29/minilm-coreml.html" rel="alternate" type="text/html" title="Embedding with MiniLM" /><published>2025-09-29T04:03:03+02:00</published><updated>2025-09-29T04:03:03+02:00</updated><id>https://jano.dev/swift/2025/09/29/minilm-coreml</id><content type="html" xml:base="https://jano.dev/swift/2025/09/29/minilm-coreml.html"><![CDATA[<p><strong>Index</strong></p>
<ul>
  <li><a href="#why-minilm">Why MiniLM?</a></li>
  <li><a href="#prepare-minilm">Prepare MiniLM</a>
    <ul>
      <li><a href="#conversion">Convert to Core ML</a></li>
      <li><a href="#tokenizer-assets">Bundle Tokenizer Assets</a></li>
    </ul>
  </li>
  <li><a href="#predicting-with-swift">Predicting with Swift</a></li>
  <li><a href="#power-chart">Power Chart</a></li>
  <li><a href="#dependencies">Dependencies</a></li>
  <li><a href="#code">Example Code</a></li>
</ul>

<p><a id="why-minilm"></a></p>
<h2>Why MiniLM?</h2>

<p>MiniLM is a compact family of Transformer models designed to capture the meaning of text in a lightweight way. The version I use, MiniLM-L6-v2, is a smaller cousin of BERT (a well-known language model). Instead of hundreds of millions of parameters, it runs on only 6 layers and produces a 384-dimensional vector (a list of 384 numbers) for each token.</p>

<p>Because it is tuned for sentence-sized text, MiniLM is especially good at representing short pieces of information (like a function signature or DocC comments). It also does a decent job keeping unusual words, such as function or variable names in code.</p>

<p>I’m using it to implement semantic search for Swift and Markdown on <a href="https://apps.apple.com/us/app/semly/id6749020576">this application</a>. In my tests, I’ve found that it produces more relevant results than OpenAI’s largest model.</p>

<p><a id="preparing-model"></a></p>
<h2>Prepare MiniLM</h2>

<p><a id="conversion"></a></p>
<h3>Convert to Core ML</h3>

<p>We could run the model directly using <a href="http://developer.apple.com/documentation/coreml/mltensor">MLTensor</a>, but converting it to Core ML makes it more suitable to run on GPU/ANE (ANE = Apple Neural Engine).</p>

<p>To convert it, I lock sequence/batch to static shapes and use FP16 for speed, then drop unused paths via tracing. Here are the highlights from <a href="https://gist.github.com/janodev/46438dc0756caf816f426a7a80385609">prepare-model.py</a>:</p>

<ul>
  <li><code>MAX_SEQ = 512</code> = Max input size. <span id="more1" title="hint1" anchorid="1">+</span></li>
</ul>
<div id="hint1" class="aside">
  <br />
  <div class="aside-text">
    <p><code>MAX_SEQ</code> is the maximum number of tokens the model was trained to handle in a single input. Transformers are trained with a fixed sequence length, chosen to balance accuracy with speed. For MiniLM that limit is 512, so we lock Core ML inputs to <code>[batch, 512]</code>. This way Swift can preallocate MLMultiArray objects once, with no dynamic resizing or guesswork about padding.</p>

    <p>Related concepts:</p>
    <ul>
      <li><dfn>Static shape</dfn>: fixed input size.</li>
      <li><dfn>Attention mask</dfn>: An array that tells the model which token positions to attend to (1) or to ignore (0). Those ignored are discarded when computing attention.</li>
      <li><dfn>Computing attention</dfn>: a step in the transformer pipeline where the model decides how much each token should focus on other tokens.</li>
    </ul>
  </div>
</div>

<ul>
  <li><code>ct.RangeDim(lower_bound=1, upper_bound=16, default=16)</code> = batch size. <span id="more2" title="hint2" anchorid="2">+</span></li>
</ul>
<div id="hint2" class="aside">
  <br />
  <div class="aside-text">
    <p>This tells Core ML to handle up to 16 inputs in one go.</p>
    <p>For each input there are multiple steps (tokenize, copy to MLMultiArray), run Core ML, return result. This carries an overhead of memory transfer, kernel launch, scheduling that is best paid once than 16 times. Therefore we tell CoreML to expect inputs to be a MLMultiArray of shape [16, 512].</p>
    <p>In embedding and semantic search we don’t expect more than 8 x 512 = 8,192 tokens, so 16 is a good balance.</p>
  </div>
</div>

<ul>
  <li>
    <p><code>compute_units=ct.ComputeUnit.ALL</code> = use CPU, GPU, or ANE –whichever is free.</p>
  </li>
  <li>
    <p><code>compute_precision=ct.precision.FLOAT16</code> = use 16-bit numbers. <span id="more3" title="hint3" anchorid="3">+</span></p>
  </li>
</ul>
<div id="hint3" class="aside">
  <br />
  <div class="aside-text">
  <p>A Core ML model package is mostly <i>tensors</i>: big tables of numbers that represent what the model has learned. Each number is a <i>weight</i>: it nudges the model toward the right output.</p>
  <p>These numbers can be stored with different precision:</p>
  <ul>
    <li>32-bit (“full precision”): very exact, but heavier and slower.</li>
    <li>16-bit (“half precision”): a little less exact, but much lighter and faster.</li>
    <li>4-bit, 8-bit: these are usually <i>quantized</i> models –models reduced to smaller sizes so they run on less powerful hardware.</li>
   </ul>
   <p>When using embeddings we are usually interested in their similarity. We calculate this with <i>cosine similarity</i>, which is a comparison between the angle of two vectors in space. If the vectors point in the same direction, they’re considered similar, regardless of their length (magnitude). Because orientation doesn’t depend on tiny decimals, 16-bit is good enough.</p>
  </div>
</div>

<ul>
  <li><code>minimum_deployment_target=ct.target.macOS14</code> generate a MLProgram. <span id="more4" title="hint4" anchorid="4">+</span></li>
</ul>
<div id="hint4" class="aside">
  <br />
  <div class="aside-text">
    The other option is generating a Neural Network, but other than compatibility there is no reason to do that. MLProgram supports more operations and is the modern format. <a href="https://apple.github.io/coremltools/docs-guides/source/comparing-ml-programs-and-neural-networks.html">Here is the difference</a>.
  </div>
</div>

<ul>
  <li><code>torch.jit.trace</code> records the exact steps the model takes when you run it once. For MiniLM we only care about turning text into embeddings, so we trace just that path and throw away the rest. The result is a smaller, faster model. <span id="more5" title="hint5" anchorid="5">+</span></li>
</ul>
<div id="hint5" class="aside">
  <br />
  <div class="aside-text">
    <p><code>torch.jit.trace</code> is a tool that runs the model once with example inputs. PyTorch watches every calculation that happens—all the matrix math, all the transformations—and records them step-by-step. That recording becomes the converted model.</p>
    <p>For MiniLM, we set it up to only output the embeddings. When we record this, we capture just the path from tokens to embeddings. Everything else the model can do is discarded.</p>
    <p>This provides several benefits:</p>
    <ul>
      <li>Smaller file: No classification heads or extra outputs.</li>
      <li>Faster to load: Less stuff to initialize on device</li>
      <li>Better optimized: Core ML can optimize a fixed structure more effectively for consumer hardware</li>
    </ul>
  </div>
</div>

<p>The following script does the download, tracing, and conversion in one go:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> MiniLM <span class="o">&amp;&amp;</span> <span class="nb">cd </span>MiniLM
python <span class="nt">-m</span> venv venv
<span class="nb">source </span>venv/bin/activate
pip <span class="nb">install</span> <span class="nt">--upgrade</span> pip
pip <span class="nb">install </span><span class="nv">torch</span><span class="o">==</span>2.5.0 torchvision torchaudio
pip <span class="nb">install </span>scikit-learn<span class="o">==</span>1.5.1
pip <span class="nb">install </span>coremltools transformers sentence-transformers
python prepare-model.py
</code></pre></div></div>

<p>The script downloads <a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">all-MiniLM-L6-v2</a>, disables a PyTorch fastpath that confuses the converter, fixes the sequence length to 512 tokens, and creates a <code>MiniLM_L6_v2.mlpackage</code> that can be loaded from the bundle.</p>

<p>Using fixed versions and settings make the resulting <code>MiniLM_L6_v2.mlpackage</code> reproducible. Leaving them to defaults could change tensor shapes or precision and make a mess with your previous data.</p>

<p><a id="tokenizer-assets"></a></p>
<h3>Bundle Tokenizer Assets</h3>

<p>The training process for the model creates a correspondence between words, tokens, and number IDs. Example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"settings" -&gt; ["set", "##ting", "##s"] -&gt; [1012, 4567, 302]
</code></pre></div></div>

<p>The index for each token is particular to this model and means nothing for any other model. Therefore along with the model we have to download the following information from huggingface:</p>
<ul>
<li><code>config.json</code>: model architecture config</li>
<li><code>tokenizer_config.json</code>: tokenizer behavior settings</li>
<li><code>tokenizer.json</code>: configuration + vocabulary + splitting rules</li>
<li><code>vocab.txt</code>: the vocabulary lookup table</li>
</ul>

<p>Looking at the configuration MiniLM-L6 is</p>
<ul>
  <li>BERT: a standard Transformer encoder stack with self-attention and feed-forward blocks,</li>
  <li>Uncased: everything gets lowercased, which degrades signal for code</li>
  <li>6 layers (six Transformer encoder blocks)</li>
  <li>Hidden size 384: each token is represented as a 384-dimensional vector</li>
  <li>Vocabulary size 30,522: the set of known WordPiece (=the BERT tokenization algorithm) tokens; words/subwords outside this set become [UNK].</li>
  <li>max sequence length 512: the longest tokenized input it can handle in one pass; shorter inputs are padded, longer ones truncated.</li>
</ul>

<p>At runtime <code>HFTokenizerAdapter</code> loads tokenizer and vocabulary through Hugging Face’s Swift AutoTokenizer. Then we call <code>encodeWithPadding(text:)</code> it:</p>

<ol>
  <li>Splits text into WordPiece subwords using the same rules as the original model.</li>
  <li>Inserts <code>[CLS]</code> at the front and <code>[SEP]</code> at the end.</li>
  <li>Truncates or pads to 512 tokens.</li>
  <li>Builds an attention mask with 1s for real tokens and 0s for padding.</li>
</ol>

<p>The resulting arrays are wrapped in MLMultiArray and passed to <code>model.prediction(…)</code> to obtain one vector per token position. Specifically we get a 3D tensor shaped <code>[batch, seq_len, hidden_size]</code> where <code>hidden_size</code> is 384 for MiniLM.</p>

<p><a id="predicting-with-swift"></a></p>
<h2>Predicting with Swift</h2>

<p><strong>1) Load the Core ML model</strong><br />We exported to mlpackage, which once compiled becomes a <code>.mlmodelc</code> file.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Load the Core ML model</span>
<span class="kd">func</span> <span class="nf">loadMiniLM</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">MLModel</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">modelURL</span> <span class="o">=</span> <span class="kt">Bundle</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="nf">url</span><span class="p">(</span><span class="nv">forResource</span><span class="p">:</span> <span class="s">"MiniLM_L6_v2"</span><span class="p">,</span> <span class="nv">withExtension</span><span class="p">:</span> <span class="s">"mlmodelc"</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="kt">NSError</span><span class="p">(</span><span class="nv">domain</span><span class="p">:</span> <span class="s">"MiniLM"</span><span class="p">,</span> <span class="nv">code</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">userInfo</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSLocalizedDescriptionKey</span><span class="p">:</span> <span class="s">"MiniLM_L6_v2.mlmodelc not found in bundle"</span><span class="p">])</span>
    <span class="p">}</span>
    <span class="k">let</span> <span class="nv">cfg</span> <span class="o">=</span> <span class="kt">MLModelConfiguration</span><span class="p">()</span>
    <span class="n">cfg</span><span class="o">.</span><span class="n">computeUnits</span> <span class="o">=</span> <span class="o">.</span><span class="n">all</span>
    <span class="k">return</span> <span class="k">try</span> <span class="kt">MLModel</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="n">modelURL</span><span class="p">,</span> <span class="nv">configuration</span><span class="p">:</span> <span class="n">cfg</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p><strong>2) Tokenize the input</strong> <span id="more6" title="hint6" anchorid="6">+</span>

<p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Tokenize the input (BERT WordPiece, uncased)</span>
<span class="kd">func</span> <span class="nf">tokenize</span><span class="p">(</span><span class="n">_</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">maxLen</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">512</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="nv">ids</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">],</span> <span class="nv">mask</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">])</span> <span class="p">{</span>
    <span class="c1">// Assumes you’ve configured the tokenizer with MiniLM’s vocab + lowercasing.</span>
    <span class="k">let</span> <span class="nv">tokenizer</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">BERTWordPieceTokenizer</span><span class="p">()</span>
    <span class="k">let</span> <span class="p">(</span><span class="nv">ids</span><span class="p">,</span> <span class="nv">mask</span><span class="p">)</span> <span class="o">=</span> <span class="n">tokenizer</span><span class="o">.</span><span class="nf">encodeWithPadding</span><span class="p">(</span><span class="nv">text</span><span class="p">:</span> <span class="n">text</span><span class="p">,</span> <span class="nv">maxLength</span><span class="p">:</span> <span class="n">maxLen</span><span class="p">)</span>
    <span class="nf">return</span> <span class="p">(</span><span class="n">ids</span><span class="p">,</span> <span class="n">mask</span><span class="p">)</span> <span class="c1">// ids/mask are length == maxLen (e.g., 512)</span>
<span class="p">}</span></code></pre></figure>
</p>

<div id="hint6" class="aside">
  <br />
  <div class="aside-text">
    <p>This step performs several operations that convert user text into tokens. The result is a list of integer IDs (from the vocabulary) plus a mask telling the model which positions are real words and which are padding to reach 512 tokens.</p>
    <ul>
      <li>
        <strong>Split words into subword tokens.</strong> 
        <br />e.g. <code>settings</code> becomes <code>["set", "##ting", "##s"]</code>
      </li> 
      <li>
        <strong>Mark begin/end</strong>
        <br />All models use CLS/SEP for this. The input sequence becomes: <br /><code>[CLS] + tokens_for_text + [SEP] + padding</code>.
      </li> 
      <li>
        <strong>Resize input to 512</strong>
        <br />This means it is truncated or padded so it results in exactly 512 tokens. To avoid computing attention for padding tokens, a companion mask is generated with 512 booleans where 0 marks the padding tokens.
      </li> 
      <li>
        <strong>Normalize tokens</strong>
        <br />MiniLM normalizes everything to lowercase.
      </li> 
      <li>
        <strong>Replace unknown tokens with <code>UNK</code></strong><br />If a token is not present in the vocabulary it is replaced with <code>UNK</code>. This is rare, because even function identifiers are decomposed into known tokens, for instance
        <br /><code>scanForChagnes()</code> -&gt; <code>[scan, ##For, ##Change, ##s]</code>
        <br /><code>HTTPRequest2XX</code> -&gt; <code>[HTTP, ##Request, ##2, ##XX]</code>
      </li>
    </ul>
  </div>
</div>

<p><strong>3) Wrap input in <code>MLMultiArray</code></strong> <span id="more7" title="hint7" anchorid="7">+</span>


<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Creates one batch of 512 tokens.</span>
<span class="kd">func</span> <span class="nf">makeInt32Array</span><span class="p">(</span><span class="n">_</span> <span class="nv">ints</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">])</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">MLMultiArray</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">arr</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">MLMultiArray</span><span class="p">(</span>
        <span class="nv">shape</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="kt">NSNumber</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="n">ints</span><span class="o">.</span><span class="n">count</span><span class="p">)],</span> 
        <span class="nv">dataType</span><span class="p">:</span> <span class="o">.</span><span class="n">int32</span>
    <span class="p">)</span>
    <span class="k">let</span> <span class="nv">ptr</span> <span class="o">=</span> <span class="n">arr</span><span class="o">.</span><span class="n">dataPointer</span><span class="o">.</span><span class="nf">assumingMemoryBound</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="kt">Int32</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="k">in</span> <span class="n">ints</span><span class="o">.</span><span class="nf">enumerated</span><span class="p">()</span> <span class="p">{</span> <span class="n">ptr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="kt">Int32</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">}</span>
    <span class="k">return</span> <span class="n">arr</span>
<span class="p">}</span></code></pre></figure>


<div id="hint7" class="aside">
  <br />
  <div class="aside-text">
    <p>Core ML’s <a href="https://developer.apple.com/documentation/coreml/mlmultiarray">MLMultiArray</a> is a container for tensors (multi-dimensional arrays of numbers). It lets us do the math (masking, summing, normalization) directly on-device, in a fast, vectorized way. Here we are just creating the array and putting the tokens inside.</p>
  </div>
</div>

<p><strong>4) Run prediction and grab token-level hidden states.</strong> <span id="more8" title="hint8" anchorid="8">+</span></p>


<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Run prediction and grab token-level hidden states</span>
<span class="kd">func</span> <span class="nf">tokenHiddenStates</span><span class="p">(</span><span class="nv">model</span><span class="p">:</span> <span class="kt">MLModel</span><span class="p">,</span> <span class="nv">inputIDs</span><span class="p">:</span> <span class="kt">MLMultiArray</span><span class="p">,</span> <span class="nv">attentionMask</span><span class="p">:</span> <span class="kt">MLMultiArray</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">MLMultiArray</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">features</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">MLDictionaryFeatureProvider</span><span class="p">(</span><span class="nv">dictionary</span><span class="p">:</span> <span class="p">[</span>
        <span class="s">"input_ids"</span><span class="p">:</span> <span class="n">inputIDs</span><span class="p">,</span>
        <span class="s">"attention_mask"</span><span class="p">:</span> <span class="n">attentionMask</span>
    <span class="p">])</span>
    <span class="k">let</span> <span class="nv">out</span> <span class="o">=</span> <span class="k">try</span> <span class="n">model</span><span class="o">.</span><span class="nf">prediction</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">features</span><span class="p">)</span>

    <span class="c1">// Prefer "last_hidden_state"</span>
    <span class="c1">// otherwise fall back to the first multiArray output.</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nv">hs</span> <span class="o">=</span> <span class="n">out</span><span class="o">.</span><span class="nf">featureValue</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="s">"last_hidden_state"</span><span class="p">)?</span><span class="o">.</span><span class="n">multiArrayValue</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">hs</span> <span class="c1">// [1, seqLen, hidden]</span>
    <span class="p">}</span>

    <span class="c1">// Fallback: find any multiArray output</span>
    <span class="k">for</span> <span class="n">name</span> <span class="k">in</span> <span class="n">out</span><span class="o">.</span><span class="n">featureNames</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">arr</span> <span class="o">=</span> <span class="n">out</span><span class="o">.</span><span class="nf">featureValue</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">name</span><span class="p">)?</span><span class="o">.</span><span class="n">multiArrayValue</span><span class="p">,</span> <span class="n">arr</span><span class="o">.</span><span class="n">shape</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">3</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">arr</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">throw</span> <span class="kt">NSError</span><span class="p">(</span>
        <span class="nv">domain</span><span class="p">:</span> <span class="s">"MiniLM"</span><span class="p">,</span> 
        <span class="nv">code</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> 
        <span class="nv">userInfo</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSLocalizedDescriptionKey</span><span class="p">:</span> <span class="s">"No 3D hidden-state output found"</span><span class="p">]</span>
    <span class="p">)</span>
<span class="p">}</span></code></pre></figure>


<div id="hint8" class="aside">
  <br />
  <div class="aside-text">
    <p>Calling <code>model.prediction(…)</code> runs the Core ML model with our inputs. Each token is turned into a <em>hidden state</em>: a vector of numbers that captures the token’s meaning in context. For MiniLM L6, each hidden state has 384 numbers. Together they form a 3D tensor of shape <code>[batch, seq_len, hidden_size]</code> where:</p>
    <ul>
      <li><code>batch</code>: number of inputs processed at once (here, 1).</li>
      <li><code>seq_len</code>: the number of tokens (here, 512).</li>
      <li><code>hidden_size</code>: the length of each token vector (384 for MiniLM).</li>
    </ul>
    <p>So the output shape <code>[1, 512, 384]</code> means: one input, with 512 tokens, each represented as a 384-dimensional vector. The model may expose this as <code>last_hidden_state</code> or <code>hidden_states</code>.</p>
  </div>
</div>

<p><strong>5) Masked mean pooling.</strong> <span id="more9" title="hint9" anchorid="9">+</span></p>


<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Masked mean pooling (to sentence/chunk vector)</span>
<span class="kd">func</span> <span class="nf">maskedMeanPool</span><span class="p">(</span><span class="nv">hs</span><span class="p">:</span> <span class="kt">MLMultiArray</span><span class="p">,</span> <span class="nv">attentionMask</span><span class="p">:</span> <span class="kt">MLMultiArray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">MLXArray</span> <span class="p">{</span>
    <span class="c1">// hs: [1, seqLen, hidden], mask: [1, seqLen]</span>
    <span class="k">let</span> <span class="nv">hsArr</span>   <span class="o">=</span> <span class="kt">MLXArray</span><span class="p">(</span><span class="nv">mlMultiArray</span><span class="p">:</span> <span class="n">hs</span><span class="p">)</span><span class="o">.</span><span class="nf">astype</span><span class="p">(</span><span class="o">.</span><span class="n">float32</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">maskArr</span> <span class="o">=</span> <span class="kt">MLXArray</span><span class="p">(</span><span class="nv">mlMultiArray</span><span class="p">:</span> <span class="n">attentionMask</span><span class="p">)</span><span class="o">.</span><span class="nf">astype</span><span class="p">(</span><span class="o">.</span><span class="n">float32</span><span class="p">)</span>

    <span class="k">let</span> <span class="nv">seqLen</span> <span class="o">=</span> <span class="n">maskArr</span><span class="o">.</span><span class="n">shape</span><span class="o">.</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="p">?</span> <span class="n">maskArr</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">:</span> <span class="mi">1</span>
    <span class="k">let</span> <span class="nv">mask3D</span> <span class="o">=</span> <span class="n">mlx</span><span class="o">.</span><span class="nf">reshape</span><span class="p">(</span><span class="n">maskArr</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="n">seqLen</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span> <span class="c1">// [1, seqLen, 1]</span>

    <span class="k">let</span> <span class="nv">masked</span> <span class="o">=</span> <span class="n">hsArr</span> <span class="o">*</span> <span class="n">mask3D</span> <span class="c1">// broadcast</span>
    <span class="k">let</span> <span class="nv">sumVec</span> <span class="o">=</span> <span class="n">mlx</span><span class="o">.</span><span class="nf">sum</span><span class="p">(</span><span class="n">masked</span><span class="p">,</span> <span class="nv">axes</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="c1">// [1, hidden]</span>
    <span class="k">let</span> <span class="nv">counts</span> <span class="o">=</span> <span class="n">mlx</span><span class="o">.</span><span class="nf">maximum</span><span class="p">(</span><span class="n">mlx</span><span class="o">.</span><span class="nf">sum</span><span class="p">(</span><span class="n">mask3D</span><span class="p">,</span> <span class="nv">axes</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">]),</span> <span class="kt">MLXArray</span><span class="p">(</span><span class="mf">1e-6</span> <span class="k">as</span> <span class="kt">Float</span><span class="p">))</span> <span class="c1">// [1,1]</span>
    <span class="k">return</span> <span class="n">sumVec</span> <span class="o">/</span> <span class="n">counts</span> <span class="c1">// [1, hidden]</span>
<span class="p">}</span></code></pre></figure>


<div id="hint9" class="aside">
  <br />
  <div class="aside-text">
    <p>After prediction, we have one vector (384 numbers) for each token. But we want a single vector that represents the entire sentence or snippet. To do this we use <dfn>mean pooling</dfn>: take the average of all the real token vectors (the ones marked with 1 in the mask). This step is called <dfn>masked mean pooling</dfn>.</p>
    <p>The result is a single 384-dimensional vector that captures the meaning of the whole input. This is the embedding we’ll normalize and use for semantic search. At this point we have one embedding that represents the entire chunk of text.</p>
  </div>
</div>

<p><strong>6) L2 normalize</strong> <span id="more10" title="hint10" anchorid="10">+</span></p>


<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// L2 normalize (cosine-ready)</span>
<span class="kd">func</span> <span class="nf">l2normalize</span><span class="p">(</span><span class="n">_</span> <span class="nv">vec</span><span class="p">:</span> <span class="kt">MLXArray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">Float</span><span class="p">]</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">eps</span>  <span class="o">=</span> <span class="kt">MLXArray</span><span class="p">(</span><span class="mf">1e-12</span> <span class="k">as</span> <span class="kt">Float</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">norm</span> <span class="o">=</span> <span class="n">mlx</span><span class="o">.</span><span class="nf">sqrt</span><span class="p">(</span>
        <span class="n">mlx</span><span class="o">.</span><span class="nf">maximum</span><span class="p">(</span>
            <span class="n">mlx</span><span class="o">.</span><span class="nf">sum</span><span class="p">(</span><span class="n">vec</span> <span class="o">*</span> <span class="n">vec</span><span class="p">,</span> <span class="nv">axes</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nv">keepDims</span><span class="p">:</span> <span class="kc">true</span><span class="p">),</span> 
            <span class="n">eps</span>
        <span class="p">)</span>
    <span class="p">)</span>
    <span class="nf">return</span> <span class="p">(</span><span class="n">vec</span> <span class="o">/</span> <span class="n">norm</span><span class="p">)</span><span class="o">.</span><span class="nf">toArray</span><span class="p">()</span> <span class="c1">// [Float], length == hidden (≈384)</span>
<span class="p">}</span></code></pre></figure>


<div id="hint10" class="aside">
  <br />
  <div class="aside-text">
    <p>After pooling, we have one vector for the whole input (≈384 numbers). The last step is <dfn>L2 normalization</dfn>. This means we scale the vector so that its length (magnitude) becomes 1, without changing its direction.</p>
    <p>Why do this? In semantic search we compare embeddings by angle (cosine similarity). If two vectors point in the same direction, they are considered similar, no matter how long they are. Normalizing makes every embedding the same length, so comparisons depend only on direction.</p>
    <p>The result is a single, normalized 384-dimensional embedding that represents the text. This is the vector you store and use for similarity search.</p>
  </div>
</div>

<p>We can now call every function above to produce an embedding for the input text.</p>


<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// End-to-end: text -&gt; normalized embedding</span>
<span class="kd">func</span> <span class="nf">miniLMEmbedding</span><span class="p">(</span><span class="n">_</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">maxLen</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">512</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">Float</span><span class="p">]</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">model</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">loadMiniLM</span><span class="p">()</span>
    <span class="k">let</span> <span class="p">(</span><span class="nv">ids</span><span class="p">,</span> <span class="nv">mask</span><span class="p">)</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">tokenize</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="nv">maxLen</span><span class="p">:</span> <span class="n">maxLen</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">inputIDs</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">makeInt32Array</span><span class="p">(</span><span class="n">ids</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">attention</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">makeInt32Array</span><span class="p">(</span><span class="n">mask</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">hs</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">tokenHiddenStates</span><span class="p">(</span>
        <span class="nv">model</span><span class="p">:</span> <span class="n">model</span><span class="p">,</span> 
        <span class="nv">inputIDs</span><span class="p">:</span> <span class="n">inputIDs</span><span class="p">,</span> 
        <span class="nv">attentionMask</span><span class="p">:</span> <span class="n">attention</span>
    <span class="p">)</span>
    <span class="k">let</span> <span class="nv">pooled</span> <span class="o">=</span> <span class="nf">maskedMeanPool</span><span class="p">(</span><span class="nv">hs</span><span class="p">:</span> <span class="n">hs</span><span class="p">,</span> <span class="nv">attentionMask</span><span class="p">:</span> <span class="n">attention</span><span class="p">)</span>
    <span class="k">return</span> <span class="nf">l2normalize</span><span class="p">(</span><span class="n">pooled</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Example:</span>
<span class="k">let</span> <span class="nv">vec</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">miniLMEmbedding</span><span class="p">(</span><span class="s">"SwiftUI view that handles user input"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"dim:"</span><span class="p">,</span> <span class="n">vec</span><span class="o">.</span><span class="n">count</span><span class="p">)</span> <span class="c1">// ~384</span></code></pre></figure>


<p>MLX keeps the post-processing on-device and vectorized: multiply by the mask, reduce across the sequence axis, divide by the live-token count, then L2 normalize. <a href="https://rudrank.com/">Rudrank Riyam</a> has a book on MLX if you are interested.</p>

<a id="power-chart"></a>
<h2>Power Chart</h2>

<p>The first conversion was running entirely on the CPU at around 13 W. Core ML defaulted there because it wasn’t sure the GPU or Neural Engine could handle certain features. For instance:</p>

<ul>
  <li><strong>Flexible input shapes</strong>. If the model accepts variable-length inputs, Core ML can’t pre-plan memory well so it keeps work on the CPU (which can handle any shape). Until recently, any model with dynamic dimensions (like <code>RangeDim</code> or 512×N) was forced to CPU. The converter now accepts them, but the runtime still prefers CPU unless you fix the shapes in advance (e.g. always 512 tokens).</li>
  <li><strong>Mixed precision</strong>. If some layers want 32-bit floats and others 16-bit, accelerators struggle to coordinate work.</li>
  <li><strong>Attention layers</strong>. Transformers rely on operations such as attention, layer norm, and GELU. On macOS, the ANE doesn’t fully support these yet.</li>
</ul>

<p>By giving Core ML a fixed input size (512 tokens), using FP16 (16-bit floats), and setting <code>computeUnits = .all</code>, the runtime can safely schedule the work on GPU.</p>

<p>With those tweaks, power use dropped to ≈ 5–6 W on CPU, ≈ 0.3–0.4 W on GPU, and 0 W on ANE. This shows how much more efficient the GPU is for transformer workloads: ≈10x less power than the CPU for the same job.</p>

<p>I think this is as good as it gets. MiniLM won’t run on the Neural Engine as-is. The ANE is more specialized than the GPU and only supports a limited set of operations. Transformers like MiniLM rely on others (attention, layer norm, GELU) that it doesn’t accelerate. To change that, Apple would need to design and train a new Transformer variant built only from ANE-friendly ops. Apple’s own Foundation models may be examples of this.</p>

<a id="dependencies"></a>
<h2>Dependencies</h2>

<p>Swift dependencies I used:</p>

<ul>
  <li><a href="https://github.com/ml-explore/mlx-swift">mlx-swift</a> (MLX, MLXNN, MLXFast, MLXLinalg, MLXRandom): Apple’s Swift bindings for MLX, providing the tensor math, neural-net layers, linear algebra, random number generation, and fast kernels used for pooling and normalization.</li>
  <li><a href="https://github.com/huggingface/swift-transformers">swift-transformers</a> (Transformers): Hugging Face’s Swift port, used here for tokenizer adapters.</li>
</ul>

<p>In the Python script torch==2.5.0, coremltools, transformers, and sentence-transformers are the pieces that actually export the graph, while scikit-learn satisfies a dependency pulled in by sentence-transformers. You need to pin these versions to generate future compatible versions if you want to experiment with conversions.</p>

<a id="code"></a>
<h2>Example Code</h2>

<a href="https://github.com/janodev/MiniLM-Embedding">github.com/janodev/MiniLM-Embedding</a>
</p></p>]]></content><author><name></name></author><category term="swift" /><summary type="html"><![CDATA[Index Why MiniLM? Prepare MiniLM Convert to Core ML Bundle Tokenizer Assets Predicting with Swift Power Chart Dependencies Example Code]]></summary></entry><entry><title type="html">Semantic Search</title><link href="https://jano.dev/swift/2025/09/28/semantic-search.html" rel="alternate" type="text/html" title="Semantic Search" /><published>2025-09-28T04:03:03+02:00</published><updated>2025-09-28T04:03:03+02:00</updated><id>https://jano.dev/swift/2025/09/28/semantic-search</id><content type="html" xml:base="https://jano.dev/swift/2025/09/28/semantic-search.html"><![CDATA[<p>I built a semantic search tool. Codex and Claude agree it’s effective.</p>

<p><b>Index</b></p>
<ul>
  <li><a href="#introduction">Introduction</a></li>
  <li><a href="#evaluation">Agent Evaluation</a></li>
  <li><a href="#embeddings">A Primer for Embeddings</a></li>
  <li><a href="#technical">Technical details</a></li>
  <li><a href="#why">Why</a></li>
</ul>

<p><a id="introduction"></a></p>
<h2>Introduction</h2>

<p>When an AI agent explores a codebase, it needs to acquire the right context to be effective. There are three complementary ways to do so:</p>

<ul>
  <li>Exact text with <code class="language-plaintext highlighter-rouge">rg</code> (ripgrep) — fastest for literals and identifiers.</li>
  <li>Structure‑aware with <code class="language-plaintext highlighter-rouge">ast-grep</code> — precise for declarations and pattern audits.</li>
  <li>Semantic search with embeddings— intent‑level queries like “user settings,” “retry logic,” or “transactional sync.”</li>
</ul>

<p>Here is a comparison search for <code class="language-plaintext highlighter-rouge">user settings</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># EXACT SEARCH: look for settings and possible synonyms</span>
rg <span class="nt">-i</span> <span class="s2">"settings|preferences|defaults|configuration"</span> <span class="nb">.</span>

<span class="c"># SYNTAX AWARE SEARCH: look for functions with settings in their name</span>
ast-grep <span class="nt">-p</span> <span class="s2">"func </span><span class="nv">$FUNC</span><span class="s2">(settings)"</span> <span class="nt">--lang</span> swift <span class="nb">.</span>

<span class="c"># SEMANTIC SEARCH</span>
semly locate <span class="s2">"user settings"</span>
</code></pre></div></div>
<p><small><i><sup>*</sup>I published <a href="https://apps.apple.com/us/app/semly/id6749020576">semly</a> last week, I’m not aware of a similar tool.</i></small></p>

<p>Mind the <code class="language-plaintext highlighter-rouge">rg</code> string of synonyms. Agents know settings may appear under multiple names so they search for alternatives. This is highly effective but not as good as semantic search. <strong>Semantic search</strong> finds code by what it <em>does</em>, not just what it’s <em>called</em>. If you search for ‘retry logic,’ it will find functions named ‘attemptWithBackoff’ or ‘handleFailureAndRetry’—even though those names don’t contain the word ‘retry.’</p>

<p>A typical result:</p>
<ul>
  <li>rg finds 47 matches across 12 files (lots of noise)</li>
  <li>ast-grep finds 3 function declarations (precise but misses conceptual matches)</li>
  <li>semly finds 8 relevant results ranked by semantic similarity</li>
</ul>

<p><a id="evaluation"></a></p>
<h2>Agent Evaluation</h2>

<p>I specifically wrote this tool to speed up project exploration afer spawning a new agent. Here is a quick test you can drop on your agent to confirm its utility:</p>

<blockquote>
  <p>compare the effectiveness of rg, ast-grep, and semly finding content in this swift project</p>
</blockquote>

<p>Results on my machine:</p>
<ul>
  <li>Claude Opus executed 6 different tests. <span id="more1" title="hint1" anchorid="claude-eval">Full evaluation.</span></li>
  <li>Codex gpt-5 high ran “a few representative searches”. <span id="more2" title="hint2" anchorid="codex-eval">Full evaluation.</span></li>
</ul>

<p>Both reached this conclusion: ripgrep for exact matches, ast-grep for structure, Semly for meaning. Finally, I asked GPT-5 to summarize both evaluations in a table comparing all three tools:</p>

<div id="hint1" class="aside">
  <br />
  <div class="aside-text">
  <div>
    <p><strong>ripgrep (rg)</strong></p>
    <p>Strengths:</p>
    <ul>
      <li>⚡ Extremely fast (~0.02–0.04s for most queries)</li>
      <li>Excellent regex support</li>
      <li>Found 122 class definitions accurately</li>
      <li>Great for literal text searches and pattern matching</li>
      <li>Low resource usage</li>
    </ul>
    <p>Weaknesses:</p>
    <ul>
      <li>No semantic understanding</li>
      <li>Can't understand code structure or intent</li>
      <li>Limited to text patterns</li>
    </ul>
  </div>
  <div>
    <p><strong>ast-grep</strong></p>
    <p>Strengths:</p>
    <ul>
      <li>Structure-aware searching</li>
      <li>Can find specific code patterns</li>
      <li>Language-aware parsing</li>
    </ul>
    <p>Weaknesses:</p>
    <ul>
      <li>Slower than ripgrep (~0.05s+)</li>
      <li>Limited Swift support</li>
      <li>Pattern syntax errors with complex queries</li>
      <li>Struggled with Swift-specific syntax like async functions</li>
    </ul>
  </div>
  <div>
    <p><strong>Semly</strong></p>
    <p>Strengths:</p>
    <ul>
      <li>🧠 Semantic understanding – finds conceptually related code</li>
      <li>Excellent for high-level queries ("error handling", "UI components")</li>
      <li>Returns contextual previews</li>
      <li>Finds relevant documentation alongside code</li>
      <li>Perfect for exploring unfamiliar codebases</li>
    </ul>
    <p>Weaknesses:</p>
    <ul>
      <li>Requires indexing upfront</li>
      <li>Not suitable for exact text matching</li>
      <li>Slower for simple searches</li>
    </ul>
  </div>
  <div>
    <p><strong>Verdict</strong></p>
    <p>Each tool excels in different scenarios:</p>
    <ul>
      <li>Use <strong>ripgrep</strong> for fast text searches, refactoring, finding specific strings.</li>
      <li>Use <strong>ast-grep</strong> for structural refactoring (better with languages other than Swift).</li>
      <li>Use <strong>Semly</strong> for understanding code architecture, finding related functionality, and exploring concepts.</li>
    </ul>
    <p>For this Swift project specifically, <strong>ripgrep + Semly</strong> is the optimal combination, with ripgrep handling precise searches and Semly providing semantic understanding.</p>
  </div>
  </div>
</div>

<div id="hint2" class="aside">
  <br />
  <div class="aside-text">
    <p><strong>Summary</strong></p>
    <ul>
      <li><strong>rg</strong>: Fastest for literal/regex searches; great for exact identifiers, logs, TODOs, quick scoping.</li>
      <li><strong>ast-grep</strong>: Structure-aware (AST) precision; best for exact declarations, audits, and safe codemods.</li>
      <li><strong>semly</strong>: Intent-level (natural language) across code+docs; best for discovery/navigation and “what/where/how” questions.</li>
    </ul>
    <p><strong>Example 1 — scanForChanges</strong></p>
    <ul>
      <li>rg (decls+calls): quickly surfaces definitions and uses.
        <pre><code>rg -n "scanForChanges(" Sources</code></pre>
        <p>Examples: <code>Sources/Semly/Services/FileTracking/FileTracker.swift:15</code>, <code>Sources/Semly/Services/CodeAnalysis/CodeAnalysisCoordinator.swift:107</code>, call sites in coordinators/routers.</p>
      </li>
      <li>ast-grep (declaration, precise):
        <pre><code>ast-grep run -l swift -p 'func scanForChanges(' Sources</code></pre>
        <p>Pinpoints declaration: <code>Sources/Semly/Services/FileTracking/FileTracker.swift:15</code></p>
      </li>
      <li>semly (navigate by intent):
        <pre><code>semly query "scanForChanges FileTracker" --project Semly --ext swift --limit 5 --mode locate</code></pre>
        <p>Returns <code>FileTracker.swift</code> plus relevant pipeline/coordinator contexts for quick jumping.</p>
      </li>
    </ul>
    <p><strong>Example 2 — Long Identifier</strong></p>
    <ul>
      <li>Identifier: <code>mergeLeadingImportChunkIfPresent</code></li>
      <li>rg: finds both call and declaration fast.
        <p><code>Sources/Semly/Threading/AnalysisPipeline/ProjectAnalysisPipelineV2.swift:803</code> (call)</p>
        <p><code>Sources/Semly/Threading/AnalysisPipeline/ProjectAnalysisPipelineV2.swift:836</code> (decl)</p>
      </li>
      <li>ast-grep (declaration, structural):
        <pre><code>ast-grep run -l swift -p 'func mergeLeadingImportChunkIfPresent(' Sources</code></pre>
        <p>Pinpoints decl: <code>Sources/Semly/Threading/AnalysisPipeline/ProjectAnalysisPipelineV2.swift:836</code></p>
      </li>
      <li>semly (deterministic locate by symbol):
        <pre><code>semly query "mergeLeadingImportChunkIfPresent" --project Semly --ext swift --limit 5 --mode locate</code></pre>
        <p>Surfaces the declaration in <code>ProjectAnalysisPipelineV2.swift</code> at top.</p>
      </li>
    </ul>
    <p><strong>Example 3 — Concept-Level “Where is transactional sync performed?”</strong></p>
    <ul>
      <li>Core idea: syncing chunk+embedding results in a DB transaction.</li>
      <li>semly (NL → code):
        <pre><code>semly query "Where is the transactional sync performed for chunks and embeddings?" --project Semly --ext swift --limit 6</code></pre>
        <p>Points to <code>DatabaseProjectAdapter.syncFileAnalysisInTransaction</code> and pipeline references.</p>
        <p>Key file: <code>Sources/Semly/Services/ProjectRegistry/DatabaseProjectAdapter.swift:240</code></p>
      </li>
      <li>rg (once you know the API):
        <pre><code>rg -n "syncFileAnalysisInTransaction" Sources</code></pre>
        <p>Example: <code>Sources/Semly/Services/ProjectRegistry/DatabaseProjectAdapter.swift:240</code></p>
      </li>
      <li>ast-grep (calls, if you craft patterns): good for auditing member/free/await/try forms; requires precise patterns and/or rules.</li>
    </ul>
    <p><strong>Strengths &amp; Limits</strong></p>
    <ul>
      <li>rg
        <ul>
          <li><em>Pros</em>: lightning fast, zero setup, great for exact strings and scoping.</li>
          <li><em>Cons</em>: no structure; noisy for conceptual questions.</li>
        </ul>
      </li>
      <li>ast-grep
        <ul>
          <li><em>Pros</em>: language-aware precision; excellent for declarations and targeted replacements/audits.</li>
          <li><em>Cons</em>: needs correct patterns/rules per language; not for vague/NL queries.</li>
        </ul>
      </li>
      <li>semly
        <ul>
          <li><em>Pros</em>: semantic discovery; cross code+docs; strong for “what/where/how” and long identifiers.</li>
          <li><em>Cons</em>: needs indexing; broad NL may prioritize docs unless you steer to code (<code>--ext swift</code> helps).</li>
        </ul>
      </li>
    </ul>
    <p><strong>Practical Workflow</strong></p>
    <ul>
      <li>Use semly first to discover and jump (intent-level).</li>
      <li>Use rg to quickly scope and enumerate concrete hits.</li>
      <li>Use ast-grep for exact structural matches and codemods.</li>
    </ul>
  </div>
</div>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Strengths</th>
      <th>Weaknesses</th>
      <th>Verdict</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><b>ripgrep</b></td>
      <td>
        <ul>
          <li>Fast (~0.02–0.04s)</li>
          <li>Regex support</li>
          <li>Literal matches</li>
          <li>Low resource usage</li>
        </ul>
      </td>
      <td>
        No awareness of code structure, intent, or semantics
      </td>
      <td>Best for exact identifiers, logs, TODOs, and quick scoping.</td>
    </tr>
    <tr>
      <td><b>ast-grep</b></td>
      <td>
        <ul>
          <li>Structure-aware searching (AST)</li>
          <li>Finds specific code patterns</li>
          <li>Language-aware parsing</li>
        </ul>
      </td>
      <td>
        <ul>
          <li>Slower than rg (~0.05s+)</li>
          <li>Limited Swift support</li>
          <li>Errors with complex patterns</li>
          <li>Struggles with Swift async syntax</li>
        </ul>
      </td>
      <td>Best for structural queries, declarations, and safe codemods.</td>
    </tr>
    <tr>
      <td><b>Semly</b></td>
      <td>
        <ul>
          <li>Semantic understanding</li>
          <li>Excellent for high-level queries</li>
          <li>Returns contextual previews</li>
          <li>Surfaces relevant docs + code</li>
          <li>Great for unfamiliar codebases</li>
        </ul>
      </td>
      <td>
        <ul>
          <li>Requires upfront indexing</li>
          <li>Not optimal for exact string matches</li>
          <li>Slower for trivial searches</li>
        </ul>
      </td>
      <td>Best for conceptual exploration, intent-level navigation, and discovery.</td>
    </tr>
  </tbody>
</table>

<p><a id="embeddings"></a></p>
<h2>Embeddings Primer</h2>

<p>When you search for “user preferences,” you want to find code about settings, configuration, and defaults—even if those exact words aren’t present. Traditional search finds only exact text matches. Semantic search finds <em>meaning</em>.</p>

<p>Here’s a quick mental model of semantic search. Imagine plotting words on a map where similar words sit close together. “duck” and “chicken” would be neighbors. “car” would be far away. If your query is “chicken,” nearby words (“duck,” “goose”) are retrieved.</p>

<p>Each word’s position on this map is stored as a pair of numbers— its x and y coordinates. These numbers are called <em>embeddings</em>. In reality, we use 300–1500 numbers instead of just 2, but the principle is the same: similar meanings = similar numbers.</p>

<p>The semantic search workflow is:</p>
<ol>
  <li>Encode information into numbers</li>
  <li>Encode query into numbers</li>
  <li>Retrieve information similar to your query</li>
</ol>

<p>The same principle can be applied to words or to chunks of text with 200 lines. The codebase indexing flow would be:</p>
<ol>
  <li>Split into chunks.</li>
  <li>Compute embeddings.</li>
  <li>Store chunks with metadata (file, line).</li>
  <li>Encode the query as an embedding.</li>
  <li>Compare via “cosine similarity”</li>
  <li>Return ranked, relevant chunks.</li>
  <li>Optionally, hand results to an LLM to interpret the answer.</li>
</ol>

<p>Here is additional background</p>
<ul>
  <li><a href="/swift/2025/09/27/LLM-Primer.html">LLM Primer</a> if you know nothing about LLMs</li>
  <li><a href="/swift/2025/09/29/minilm-coreml.html">Embedding with MiniLM</a> if you want to embed using Swift</li>
</ul>

<p><a id="technical"></a></p>
<h2>Technical Details</h2>

<h3>Parsing Strategy</h3>

<p>To parse Markdown and Swift I use <a href="https://github.com/swiftlang/swift-markdown">swift-markdown</a> and <a href="https://github.com/swiftlang/swift-syntax">SwiftSyntax</a>. I only support those two but I think <a href="https://github.com/tree-sitter/tree-sitter">Tree-sitter</a> would make multi-language support straightforward.</p>

<p>These libraries tells me the structure, so I can split the file preserving related elements together. For instance, headings stay with their content, code examples remain intact, etc. In other words I’m splitting following semantic boundaries. The goal is fine-grained chunks that differentiate components while preserving local reasoning.</p>

<h3>Embedding Model Choice</h3>

<p>I used on-device <a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">MiniLM L6 v2</a>. I described how in <a href="/swift/2025/09/29/minilm-coreml.html">Embedding with MiniLM</a>.</p>

<p>MiniLM outperforms OpenAI’s larger models for code because:</p>
<ul>
  <li>It is tuned for sentence-sized text. This plays well with one-liners like user queries, markdown headings, DocC comments, function signatures.</li>
  <li>It tends to preserve the distinctiveness of rare tokens (like type and function names).</li>
  <li>It runs fast on-device, so I can index at finer granularity and re-rank more candidates without significant latency.</li>
</ul>

<h3>Ranking &amp; Relevance</h3>
<ul>
  <li>Symbol pinning: ensure exact camelCase identifiers rank top. e.g. if user queries <code class="language-plaintext highlighter-rouge">scanForChanges</code>, that function, if found, ranks top.</li>
  <li>Header-lex boosts: reward overlaps between query terms and signatures/headings.</li>
  <li>Lexical prefilter: discard candidates without token overlap.</li>
</ul>

<p>In practice, there are a lot more knobs to tune in A/B tests, and it’s impossible to predict success before running experiments. There are formal metrics to rank results but without a labeled dataset I just eyeball the results.</p>

<h3>Infraestructure</h3>

<p>Storage</p>
<ul>
  <li>Plain SQLite with GRDB. Not even sqlite-vec.</li>
  <li>CPU/GPU/ANE cost is negligible. No need for a vector DB unless you’re at GitHub scale.</li>
</ul>

<p>Communication terminal/app</p>
<ul>
  <li>Automator remains the less hacky option for MAS.</li>
  <li>Other options considered: XPC, app groups, defaults, URL + TCP port, tmp files.</li>
  <li>XPC for the notarized version but it requires a global mach name, which is disallowed in MAS.</li>
</ul>

<p><a id="why"></a></p>
<h2>Why</h2>

<p>While working on Semly I created 100+ markdown documents about the application itself. My workflow was</p>

<blockquote>
  <p>“Claude read x,y,z then work on files a,b,c“.</p>
</blockquote>

<p>Now I tell the agent “use semly” –which is unfortunate, but I find that agents don’t automatically reach for external tools.</p>

<p>I rarely read every line of code. Instead, I browse on Sourcetree (!) track architecture, write tests, log experiments, and step in when the agent goes astray. I’ve noticed agents struggle most with what they can’t see directly. Some examples I suffered, from worse to better: graphics with multiple coordinate systems &gt; generics &gt; inheritance &gt; composition &gt; Entity Component System (ECS). I found ECS very effective, giant state + reducers = no problem for agents.</p>

<p>So far, Semly is a workflow enhancer, not a necessity. Likely it will save some tokens and you won’t even notice. But I see no reason not to use it. I think in the near future agents will run semantic search hidden in the background. It’s also likely we will be able to create complete technical documentation for projects –the technology for it is already here; but that’s another story.</p>

<p><a href="https://apps.apple.com/us/app/semly/id6749020576">Semly</a> is available as CLI and MCP. MCP is like dropping the <a href="/files/semly-help.txt">whole manual</a> in the context for better or worse. If you use Claude Code:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude mcp add semly <span class="nt">--scope</span> user <span class="nt">--</span> semly mcp
claude mcp remove <span class="s2">"semly"</span> <span class="nt">-s</span> user
</code></pre></div></div>]]></content><author><name></name></author><category term="swift" /><summary type="html"><![CDATA[I built a semantic search tool. Codex and Claude agree it’s effective.]]></summary></entry><entry><title type="html">LLM Primer</title><link href="https://jano.dev/swift/2025/09/27/LLM-Primer.html" rel="alternate" type="text/html" title="LLM Primer" /><published>2025-09-27T04:03:03+02:00</published><updated>2025-09-27T04:03:03+02:00</updated><id>https://jano.dev/swift/2025/09/27/LLM-Primer</id><content type="html" xml:base="https://jano.dev/swift/2025/09/27/LLM-Primer.html"><![CDATA[<p>Some LLM background info I wrote for the article Semantic Search.</p>

<p><b>Index</b></p>
<ul>
  <li><a href="#llm-primer">LLM Primer</a>
    <ul>
      <li><a href="#what-is-an-llm">What is an LLM?</a></li>
      <li><a href="#training">Training</a></li>
      <li><a href="#fine-tuning">Fine Tuning</a></li>
      <li><a href="#chat-completion">Chat Completion</a></li>
    </ul>
  </li>
  <li><a href="#context-in-big-codebases">Context in Big Codebases</a>
    <ul>
      <li><a href="#files">Files</a></li>
      <li><a href="#embeddings">Embeddings</a></li>
      <li><a href="#fine-tuning-codebase">Fine-Tuning</a></li>
      <li><a href="#cost-comparison">Cost comparison</a></li>
    </ul>
  </li>
</ul>

<p><a id="llm-primer"></a></p>
<h2>LLM Primer</h2>

<p><a id="what-is-an-llm"></a></p>
<h3>What is an LLM?</h3>

<p>A <strong>Large Language Model</strong> is an artificial intelligence designed to recognize patterns in language and produce contextually appropriate responses to queries.</p>

<ul>
  <li>They are Large because they are trained with curated data that amounts to billions of words – the equivalent of reading millions of books, articles, and documents.</li>
  <li>They are Language Models because they have been trained to understand human created languages.</li>
</ul>

<p>Even when LLMs are trained with text, the same principles can be applied to train models on movement recordings, musical notes, images, or any other form of sequential data. The key is whether there are patterns that express relationships the model can learn from</p>

<p><a id="training"></a></p>
<h3>Training</h3>

<p>Training a large language model (LLM) involves teaching it to predict the next word in a sequence of text. It involves several key steps:</p>

<ol>
  <li>Tokenization</li>
  <li>Embeddings</li>
  <li>Training loop on a neural network</li>
</ol>

<p><strong>Tokenization</strong>: the text is broken down into tokens, which are numerical representations of words or subwords. Tokens are the basic unit of text that AI models process - smaller than words but larger than individual characters. For instance, the word “hamburger” might be split into tokens like ham–burg–er. Most LLMs have a maximum number of tokens they can handle in a single conversation (the context window). A context window of 32k tokens corresponds to roughly 20–25k English words.</p>

<p><strong>Embeddings</strong> are arrays of numbers (vectors) that can represent any structured data—words, images, code, musical notes, etc. Think of them as arrows pointing at a point in space. These vectors are designed so that “similar” pieces of data have embeddings close to each other, making it easy to retrieve related information by comparing vectors.</p>

<p>The <strong>Training loop</strong> is the iterative process through which a neural network learns from data by repeatedly processing examples, comparing predictions to correct answers, and adjusting its parameters to improve performance. Here are the key steps:</p>

<ol>
  <li>Initialize
    <ul>
      <li>Weights start with random values.</li>
      <li>Biases start at zero or small random values.</li>
    </ul>
  </li>
  <li>Forward Pass
    <ul>
      <li>Input data flows through the network.</li>
      <li>The model makes a prediction using the current weights and biases.</li>
    </ul>
  </li>
  <li>Calculate Error (Loss)
    <ul>
      <li>Compare the model’s prediction to the correct answer.</li>
      <li>The difference is the “loss.”</li>
    </ul>
  </li>
  <li>Backpropagation
    <ul>
      <li>The loss is propagated backward through the network.</li>
      <li>Each weight and bias receives a gradient indicating how it should change to reduce the loss.</li>
    </ul>
  </li>
  <li>Optimization Step
    <ul>
      <li>An optimizer uses these gradients to update each parameter.</li>
      <li>The learning rate decides how big these updates are.</li>
    </ul>
  </li>
  <li>Repeat
    <ul>
      <li>This cycle runs many times over the training data.</li>
      <li>Over time, parameters converge to values that yield accurate predictions.</li>
    </ul>
  </li>
</ol>

<p>It’s like turning lots of small knobs (the weights and biases) a little at a time to
achieve the best outcome.</p>

<p>Quick summary: chop text into little pieces, place them on a coordinate space, and create a function that looks up your queries in that space to produce an answer. That’s the (simplified) idea. Vectors are really arrows in space, except they may have 300 or 1500 dimensions instead of two or three.</p>

<p><a id="fine-tuning"></a></p>
<h3>Fine Tuning</h3>

<p><strong>Fine-tuning</strong> is the process of further training a pre-trained model on a smaller dataset to adapt it for a particular domain while preserving its general capabilities.</p>

<p>Fine-tuning modifies the model’s weights/parameters themselves through additional training on your specific data. This means the model learns patterns and knowledge from your training data and incorporates them into its base capabilities. The fine-tuning process will likely capture general patterns and repeated structures, but it may not reliably memorize every specific detail of every function, specially rarely seen ones. The agent will have to use its context window as working memory to materialize its knowledge.</p>

<p><a id="chat-completion"></a></p>
<h3>Chat Completion</h3>

<p>A <strong>chat completion</strong> is a response generated by an LLM in the context of a conversation. The model keeps track of previous messages to maintain context
and generate coherent replies.</p>

<p>The <strong>context window</strong> is the maximum amount of tokens a model can handle per
conversation.</p>
<ul>
  <li>Each user message and any additional information (e.g., instructions, attached files) consume tokens, all of which must fit into the context window.</li>
  <li>Language models can’t exceed their context window because they’re architecturally designed and trained to handle only a specific maximum length of text.</li>
  <li>The attention mechanism itself (how tokens relate to each other) grows quadratically (n²) with context length. But the overall challenge of training grows exponentially because of compounding factors like data requirements, stability issues, hardware needs, etc.</li>
  <li>Modern models may have up to a 200k token context window—meaning they can handle a large chunk of text, but not beyond it.</li>
</ul>

<p>A model that performs chat completion only has awareness of what is inside its context window. Creating a project with files in Claude or GPT is the equivalent of copy pasting such files at the beginning of the conversation. <em>Each subsequent message from the user will re-send the whole conversation up to that point, increasing the token consumption</em>. Therefore the initial files are “paid for” multiple times in terms of token usage, since they’re sent with each new message.</p>

<p>Once the conversation grows too large, some earlier context may be “pushed out” or truncated, making it inaccessible to the model. Another issue is that LLM performance degrades on long conversations. It’s like they spent most of their attention reading the whole conversation instead of solving the problem. A solution is to ask them to summarize the problem and copy paste that into a new window.</p>

<p>A workaround is to implement “sparse attention” where one token doesn’t relate to every other token, therefore avoiding the n² problem but degrading performance. Another is to compact the conversation keeping the parts relevant for ongoing work. But it remains true that attention is limited.</p>

<p><a id="context-in-big-codebases"></a></p>
<h2>Context in Big Codebases</h2>

<p>LLMs have practical limits. They can only “see” as much text as fits into their context window, and they’re not automatically aware of everything in a massive code repository. Context windows can be easily exceeded by large application codebases.</p>

<p>With these in mind there are three ways we work with LLMs today: files, embeddings, fine-tuning.</p>

<p><a id="files"></a></p>
<h3>Files</h3>

<p>For trivial cases, select only the minimal set of files needed to provide context about the problem. This reduces context window usage and ensures the model focuses on the most relevant code. Local reasoning and modularity are highly desirable, and lets you drop entire libraries on the best performance model for architectural advice.</p>

<p>For instance, the following script concatenates all <code class="language-plaintext highlighter-rouge">.swift</code> files in current folder and subfolders, then stores the result in a single file within the parent folder:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nb">.</span> <span class="nt">-name</span> <span class="s2">"*.swift"</span> <span class="nt">-type</span> f <span class="nt">-print0</span> | xargs <span class="nt">-0</span> <span class="nb">cat</span> <span class="o">&gt;</span> ../concatenated.swift
</code></pre></div></div>

<p><a id="embeddings"></a></p>
<h3>Embeddings</h3>

<p>If you have sections of code that rarely change—such as a library—you can store them as embeddings. Then, when you need to query the agent about a specific part of that code, you retrieve the relevant chunks and include them in your query. This approach avoids re-uploading the entire library and helps keep the context window usage low.</p>

<p>Here is the workflow:</p>
<ol>
  <li>Break down the codebase
    <ul>
      <li>Split code into smaller chunks, ideally chunks that make sense independently.</li>
      <li>Convert each chunk into an embedding vector and store it in a database. These vectors represent the relation between structured information.</li>
      <li>Store the vectors in a regular database (SQLite) or a vector database (Pinecone, Weaviate, Qdrant, Milvus, Redis).</li>
    </ul>
  </li>
  <li>Form the Query
    <ul>
      <li>Convert your user query into an embedding vector.</li>
    </ul>
  </li>
  <li>Retrieve &amp; Provide Context
    <ul>
      <li>Fetch the most relevant chunks from the database.</li>
      <li>Supply these chunks, alongside your question, into the model’s context. All products provide an API you can use to get the chunks for a query, then you can locally send them to the chat completion model.</li>
    </ul>
  </li>
</ol>

<p>Hopefully, this will result in low token consumption but with enough relevant code to answer the query. This ensures the model only sees the most relevant code sections, even when the overall codebase is too large to fit in the context window. For instance, when prompting about a class it may fetch surrounding code so the agent can make sense of the problem.</p>

<p><a id="fine-tuning-codebase"></a></p>
<h3>Fine-Tuning</h3>

<p>If your project is especially domain-specific or you repeatedly need the model to handle the same queries, you might consider fine-tuning the model on your codebase or on specific usage patterns. This can help the model internalize common functions and code structures. However, fine-tuning alone will not memorize every detail of a massive repository. You’ll likely still need embeddings for deeper searches.</p>

<p><a id="cost-comparison"></a></p>
<h3>Cost comparison</h3>

<p>Cost comparison</p>
<ul>
  <li>Files: no upfront costs, may result in being expensive when providing a large amount of texts.</li>
  <li>Embeddings: one-time cost in time, practically free in money.</li>
  <li>Fine-tuning: high upfront cost in time and money. Lowest token usage per query since the model “knows” your code.</li>
</ul>

<p>When to use them</p>
<ul>
  <li>Files: casual queries where you manually select the context.</li>
  <li>Embeddings: ongoing queries for mostly stable files. Requires setting up a database and embedding pipeline.</li>
  <li>Fine-tuning: domains that rarely change where you anticipate usage amortizing the cost over time.</li>
</ul>]]></content><author><name></name></author><category term="swift" /><summary type="html"><![CDATA[Some LLM background info I wrote for the article Semantic Search.]]></summary></entry><entry><title type="html">@isolated(any) Function Types</title><link href="https://jano.dev/swift/2025/08/05/isolated-any.html" rel="alternate" type="text/html" title="@isolated(any) Function Types" /><published>2025-08-05T04:03:03+02:00</published><updated>2025-08-05T04:03:03+02:00</updated><id>https://jano.dev/swift/2025/08/05/isolated-any</id><content type="html" xml:base="https://jano.dev/swift/2025/08/05/isolated-any.html"><![CDATA[<h1 id="what-is-isolatedany">What is @isolated(any)</h1>

<p><code class="language-plaintext highlighter-rouge">@isolated(any)</code> is a function attribute that preserves the isolation information of a function.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@MainActor</span> 
<span class="kd">func</span> <span class="nf">updateUI</span><span class="p">()</span> <span class="p">{</span> 
    <span class="nf">print</span><span class="p">(</span><span class="s">"I’m main actor isolated"</span><span class="p">)</span> 
<span class="p">}</span>

<span class="k">let</span> <span class="nv">f</span><span class="p">:</span> <span class="kd">@isolated</span><span class="p">(</span><span class="kd">any</span><span class="p">)</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="o">=</span> <span class="n">updateUI</span>
<span class="nf">print</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">isolation</span><span class="p">)</span> <span class="c1">// Optional(Swift.MainActor)</span></code></pre></figure>

<p>When you set a function defined with <code class="language-plaintext highlighter-rouge">@isolated(any)</code> the isolation information is no longer type-erased, and can be queried through the property <code class="language-plaintext highlighter-rouge">isolation: (any Actor)?</code>. The type reflects the three possible isolations of Swift 6 functions:</p>

<ul>
  <li>non-isolated (<code class="language-plaintext highlighter-rouge">nil</code>)</li>
  <li>isolated to an actor</li>
  <li>or isolated to global actor</li>
</ul>

<p>You can operate with this type as usual in ifs, switch, etc</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="k">let</span> <span class="nv">isolation</span><span class="p">:</span> <span class="kd">any</span> <span class="kt">Actor</span> <span class="o">=</span> <span class="n">fn</span><span class="o">.</span><span class="n">isolation</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">isolation</span><span class="p">)</span>	
<span class="p">}</span>

<span class="k">switch</span> <span class="n">fn</span><span class="o">.</span><span class="n">isolation</span> <span class="p">{</span>
<span class="k">case</span> <span class="nv">nil</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"Function is non-isolated"</span><span class="p">)</span>
<span class="k">case</span> <span class="k">let</span> <span class="nv">actor</span><span class="p">?:</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"Function is isolated to: </span><span class="se">\(</span><span class="kd">actor</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>The primary motivation for <code class="language-plaintext highlighter-rouge">@isolated(any)</code> is to make smart scheduling decisions. Before <code class="language-plaintext highlighter-rouge">@isolated(any)</code>, Task creation APIs started on the global executor and then hopped on the appropriate actor:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">Task</span> <span class="p">{</span>
    <span class="c1">// at this point we are on the global executor</span>
    <span class="c1">// and about to hop to myActor</span>
    <span class="k">await</span> <span class="n">myActor</span><span class="o">.</span><span class="nf">work</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>But with <code class="language-plaintext highlighter-rouge">@isolated(any)</code> work is synchronously enqueued on the appropriate executor. Benefits:</p>

<ul>
  <li>Better performance: no unnecessary executor hops</li>
  <li>Ordering guarantees: tasks enqueue in creation order on the actor</li>
</ul>

<h1 id="always-await">Always await</h1>

<p>Functions that carry <code class="language-plaintext highlighter-rouge">@isolated(any)</code> are always called with <code class="language-plaintext highlighter-rouge">await</code>.</p>

<p>When you call an <code class="language-plaintext highlighter-rouge">@isolated(any)</code> function, the compiler doesn’t know if:</p>

<ul>
  <li>It’s non-isolated (would run immediately)</li>
  <li>It’s isolated to the current actor (would run immediately)</li>
  <li>It’s isolated to a different actor (needs to hop to that actor)</li>
</ul>

<p>Since the compiler can’t know which case applies, it must assume the worst case - that the function needs to hop to a different actor. Because of this:</p>

<ol>
  <li>The call requires await because switching actors is an async operation</li>
  <li>It’s treated as crossing an isolation boundary - with all the sendability requirements that entails.</li>
</ol>

<p>Synchronous functions are no exception, if they carry <code class="language-plaintext highlighter-rouge">@isolated(any)</code> they must be awaited:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">executeOperation</span><span class="p">(</span><span class="n">_</span> <span class="nv">operation</span><span class="p">:</span> <span class="kd">@isolated</span><span class="p">(</span><span class="kd">any</span><span class="p">)</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">String</span><span class="p">)</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nf">operation</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">@MainActor</span>
<span class="kd">func</span> <span class="nf">op</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="p">{</span>
    <span class="s">""</span>
<span class="p">}</span>

<span class="k">await</span> <span class="nf">executeOperation</span><span class="p">(</span><span class="n">op</span><span class="p">)</span></code></pre></figure>

<h1 id="sendability-rules">Sendability rules</h1>

<p>The rules for sendability are nuanced:</p>

<ul>
  <li>The function must be Sendable if it’s async and the context isn’t known to be non-isolated</li>
  <li>Arguments and results follow standard sendability rules for cross-isolation calls</li>
  <li>Non-async <code class="language-plaintext highlighter-rouge">@isolated(any)</code> functions in non-isolated contexts don’t require sendability</li>
</ul>

<p>Before <code class="language-plaintext highlighter-rouge">@isolated(any)</code>, passing functions across isolation boundaries had three drawbacks:</p>

<ul>
  <li>The function type had to be async to switch isolation internally</li>
  <li>Non-Sendable values couldn’t be passed even when safe</li>
  <li>The isolation was completely erased with no way to recover it</li>
</ul>

<h1 id="examples">Examples</h1>

<p>Each task starts on operation’s preferred executor:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="n">parallelMap</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">,</span> <span class="kt">U</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">_</span> <span class="nv">items</span><span class="p">:</span> <span class="p">[</span><span class="kt">T</span><span class="p">],</span>
    <span class="nv">operation</span><span class="p">:</span> <span class="kd">@isolated</span><span class="p">(</span><span class="kd">any</span><span class="p">)</span> <span class="p">(</span><span class="kt">T</span><span class="p">)</span> <span class="k">async</span> <span class="o">-&gt;</span> <span class="kt">U</span>
<span class="p">)</span> <span class="k">async</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">U</span><span class="p">]</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nf">withTaskGroup</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="p">(</span><span class="kt">Int</span><span class="p">,</span> <span class="kt">U</span><span class="p">)</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">group</span> <span class="k">in</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">item</span><span class="p">)</span> <span class="k">in</span> <span class="n">items</span><span class="o">.</span><span class="nf">enumerated</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">group</span><span class="o">.</span><span class="n">addTask</span> <span class="p">{</span>
                <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="k">await</span> <span class="nf">operation</span><span class="p">(</span><span class="n">item</span><span class="p">))</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="c1">// ... collect results</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Handlers that complete on the preferred caller’s isolation:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">NetworkEventHandler</span><span class="p">:</span> <span class="kt">EventHandler</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">handle</span><span class="p">(</span>
        <span class="nv">event</span><span class="p">:</span> <span class="kt">Event</span><span class="p">,</span>
        <span class="nv">completion</span><span class="p">:</span> <span class="kd">@isolated</span><span class="p">(</span><span class="kd">any</span><span class="p">)</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="c1">// Process on background</span>
            <span class="k">await</span> <span class="nf">processEvent</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>

            <span class="c1">// Complete on caller's isolation</span>
            <span class="nf">completion</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Other examples:</p>

<ul>
  <li>Library authors writing task-spawning APIs</li>
  <li>Framework developers building concurrency abstractions</li>
  <li>Performance-critical code avoiding unnecessary executor hops</li>
  <li>Debugging tools that need isolation awareness</li>
</ul>

<h1 id="cant-schedule">Can’t schedule</h1>

<p>While <code class="language-plaintext highlighter-rouge">@isolated(any)</code> exposes isolation information, it doesn’t provide APIs to schedule work on that isolation from synchronous contexts. The Swift runtime has this capability internally but doesn’t expose it publicly.</p>

<p>This means you can inspect isolation and pass it to APIs like <a href="https://developer.apple.com/documentation/swift/task/init(name:priority:operation:)-2dll5">Task.init</a>, but you can’t implement your own scheduling on dynamic isolation. For library authors needing this capability, the current workaround is to use <code class="language-plaintext highlighter-rouge">@MainActor</code> or require explicit isolation.</p>

<p>Here’s an imaginary example of what we can’t do but would like to:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="n">withObservationTracking</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">apply</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">,</span> <span class="nv">onChange</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>

 <span class="kd">func</span> <span class="n">observeValue</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">operation</span><span class="p">:</span> <span class="kd">@isolated</span><span class="p">(</span><span class="kd">any</span><span class="p">)</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">AsyncStream</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>
     <span class="kt">AsyncStream</span> <span class="p">{</span> <span class="n">continuation</span> <span class="k">in</span>
         <span class="n">withObservationTracking</span> <span class="p">{</span>
             <span class="n">_</span> <span class="o">=</span> <span class="nf">operation</span><span class="p">()</span>
         <span class="p">}</span> <span class="nv">onChange</span><span class="p">:</span> <span class="p">{</span>
             <span class="k">let</span> <span class="nv">isolation</span> <span class="o">=</span> <span class="n">operation</span><span class="o">.</span><span class="n">isolation</span>
             
             <span class="c1">// I would like to do this, but I can’t dynamically schedule</span>
             <span class="c1">// on an isolation that is only known at runtime.</span>
             <span class="n">isolation</span><span class="o">.</span><span class="n">schedule</span> <span class="p">{</span> <span class="c1">// 'schedule' does not exist</span>
                 <span class="k">let</span> <span class="nv">newValue</span> <span class="o">=</span> <span class="nf">operation</span><span class="p">()</span>
                 <span class="n">continuation</span><span class="o">.</span><span class="nf">yield</span><span class="p">(</span><span class="n">newValue</span><span class="p">)</span>
             <span class="p">}</span>             

             <span class="c1">// instead, I can always schedule in main</span>
             <span class="c1">// whether my clients like it or not</span>
             <span class="kt">Task</span> <span class="p">{</span> <span class="kd">@MainActor</span> <span class="k">in</span>
                 <span class="k">let</span> <span class="nv">newValue</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">operation</span><span class="p">()</span>
                 <span class="n">continuation</span><span class="o">.</span><span class="nf">yield</span><span class="p">(</span><span class="n">newValue</span><span class="p">)</span>
             <span class="p">}</span>
         <span class="p">}</span>
     <span class="p">}</span>
 <span class="p">}</span>

<span class="c1">// I wanted observations to run on this actor but can’t</span>
<span class="kd">actor</span> <span class="kt">DataModel</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">count</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="kd">func</span> <span class="nf">getCount</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>
        <span class="n">count</span> 
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">model</span> <span class="o">=</span> <span class="kt">DataModel</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">stream</span> <span class="o">=</span> <span class="nf">observeValue</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">getCount</span><span class="p">)</span>

<span class="c1">// I can see you are calling with actor X, </span>
<span class="c1">// but I can’t dynamically schedule on it.</span></code></pre></figure>

<h1 id="links">Links</h1>

<ul>
  <li>NSHipster <a href="https://nshipster.com/isolated-any/">@isolated(any)</a> I wrote mine after reading this to understand it better</li>
  <li>Failed backport of v26 <a href="https://gist.github.com/janodev/d0f8e6432afd15ae20d886c3a0e51519">Observations</a> I learnt there is no public API to schedule this on arbitrary actors</li>
  <li><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0431-isolated-any-functions.md">SE-0431 @isolated(any) Function Types</a></li>
</ul>]]></content><author><name></name></author><category term="swift" /><summary type="html"><![CDATA[What is @isolated(any) @isolated(any) is a function attribute that preserves the isolation information of a function.]]></summary></entry><entry><title type="html">More Swift 6 Migration Errors</title><link href="https://jano.dev/apple/macos/swift/2025/03/09/Swift-6-Migration-Errors.html" rel="alternate" type="text/html" title="More Swift 6 Migration Errors" /><published>2025-03-09T03:03:03+01:00</published><updated>2025-03-09T03:03:03+01:00</updated><id>https://jano.dev/apple/macos/swift/2025/03/09/Swift-6-Migration-Errors</id><content type="html" xml:base="https://jano.dev/apple/macos/swift/2025/03/09/Swift-6-Migration-Errors.html"><![CDATA[<div style="display: flex; flex-direction: column;">
  <!-- Table of Contents title at the top -->
  <div>
    <h2>Table of Contents</h2>
  </div>
  
  <!-- Middle row with TOC content and image side by side -->
  <div style="display: flex; flex-direction: row; margin-top: 10px; margin-bottom: 20px;">
    <!-- TOC content on the left -->
    <div class="toc" style="flex: 1; margin-right: 20px;">
      <ul>
        <li><a href="#deinit">deinit</a></li>
        <li><a href="#async-errors">Asynchronous Function Call Errors</a></li>
        <li><a href="#sendability">Sendability and Data Race Protections</a></li>
        <li><a href="#actor-isolated-protocol">Actor Isolated Protocols</a></li>
        <li><a href="#mainactor">@MainActor Isolation Violations</a></li>
        <li><a href="#sendable-errors">Sendable-Related Errors</a>
          <ul>
            <li><a href="#type-conformance">Type Conformance</a></li>
            <li><a href="#non-sendable">Non-sendable crossing boundaries</a></li>
            <li><a href="#weak-var">Weak var is mutable</a></li>
            <li><a href="#sending-value">Sending value risks causing data races</a></li>
            <li><a href="#sending-closure">Sending closure risks causing data races</a></li>
            <li><a href="#sending-mutex">'inout sending' parameter task-isolated</a></li>
            <li><a href="#sending-captures">Captures in a @Sendable closure</a></li>
            <li><a href="#isolated-value">Isolated value cannot be sent</a></li>
          </ul>
        </li>
        <li><a href="#global-state">Global state</a></li>
        <li><a href="#singleton">Singleton</a></li>
        <li><a href="#concurrency">Concurrency</a></li>
      </ul>
    </div>
    
    <!-- Image on the right - Only visible on desktop -->
    <div style="flex: 0 0 300px; margin-top: 20px;" class="desktop-only-image">
      <img width="300" src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3hya3F0eWdnZGpodWplbDZ0andoa25uNzZ6NTgwejAzZ3Q5ZjlhYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/U7Uxq9Lyz02oStvUIE/giphy.gif" />
    </div>
  </div>
  
  <!-- Paragraph at the bottom, spanning full width -->
  <div style="width: 100%;">
    <p>Swift 6 migration is a burden because thread-safety is unrelated to the feature you are implementing. All you can do is knowing how to deal with it, including when to ignore it. Also, a warning: this stuff is boring. If you want the gist of it:</p>
    <blockquote>Don't expose non-Sendables to more than one isolation domain.</blockquote>
  </div>
</div>

<!-- Add this CSS to hide the image on mobile devices -->
<style>
  @media (max-width: 768px) {
    .desktop-only-image {
      display: none !important;
    }
  }
</style>

<div style="clear: both;"></div>

<p><a id="deinit"></a></p>
<h1 id="deinit"><strong>deinit</strong></h1>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@MainActor</span>
<span class="kd">class</span> <span class="kt">Bar</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">observer</span><span class="p">:</span> <span class="kt">NSObjectProtocol</span><span class="p">?</span>
    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="c1">// 💥 Cannot access property 'observer' with a non-sendable </span>
        <span class="c1">// type '(any NSObjectProtocol)?' from nonisolated deinit</span>
        <span class="n">observer</span> <span class="o">=</span> <span class="kc">nil</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p><strong>Class deinitializers</strong> are solved in Swift 6.1 <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0371-isolated-synchronous-deinit.md">SE-0371 Isolated synchronous deinit</a>. The following is implemented in Swift 6.1, which as of Xcode 16.2 is not available.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@MainActor</span>
<span class="kd">class</span> <span class="kt">MyViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">observer</span><span class="p">:</span> <span class="kt">NSObjectProtocol</span><span class="p">?</span>

    <span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">nibName</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">bundle</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
        <span class="n">observer</span> <span class="o">=</span> <span class="kt">NotificationCenter</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="nv">forName</span><span class="p">:</span> <span class="o">...</span>
    <span class="p">}</span>

    <span class="k">isolated</span> <span class="kd">deinit</span> <span class="p">{</span> <span class="c1">// &lt;-- add isolated here</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">observer</span> <span class="p">{</span>
            <span class="kt">NotificationCenter</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">removeObserver</span><span class="p">(</span><span class="n">observer</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In Swift 6.1 the <code class="language-plaintext highlighter-rouge">isolated</code> keyword for <code class="language-plaintext highlighter-rouge">deinit</code> will inherit the actor isolation of the containing class (in this case @MainActor), ensuring the cleanup happens on the main thread.</p>

<p>But for the time being, here is a workaround:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// non isolated</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">TaskHolder</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">task</span><span class="p">:</span> <span class="kt">Task</span><span class="o">&lt;</span><span class="kt">Void</span><span class="p">,</span> <span class="kt">Never</span><span class="o">&gt;</span><span class="p">?</span>

    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="n">task</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// isolated</span>
<span class="kd">@MainActor</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">Foo</span> <span class="p">{</span>
    <span class="c1">// var task: Task&lt;Void, Never&gt;?</span>
    <span class="k">let</span> <span class="nv">taskHolder</span> <span class="o">=</span> <span class="kt">TaskHolder</span><span class="p">()</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">TaskHolder</code>’s deinit is implicitly isolated to @MainActor since it’s created in a main actor context. When <code class="language-plaintext highlighter-rouge">LoadingViewModel</code> is deallocated, <code class="language-plaintext highlighter-rouge">deinit</code> will be called on the main thread, safely cancelling the task.</p>

<p><a id="async-errors"></a></p>
<h1 id="asynchronous-function-call-errors"><strong>Asynchronous Function Call Errors</strong></h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Call to asynchronous function 'foo()' in a synchronous function
Error: Missing 'await' in call to asynchronous function
Error: Expression is 'async' but is not marked with 'await'
</code></pre></div></div>

<p>These errors arise when you try to call an async function without:</p>

<ul>
  <li>Marking the calling function as asynchronous, or</li>
  <li>Using the <code class="language-plaintext highlighter-rouge">await</code> keyword to handle the asynchronous call properly.</li>
  <li>In Swift’s strict concurrency model, every asynchronous operation must be explicitly awaited. This enforces clarity around when and where suspension points occur.</li>
</ul>

<p>How to Fix It:</p>

<ul>
  <li>Update your function signature to be async if it needs to call async functions.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">await</code> when calling async functions, ensuring that you are handling potential suspension correctly.</li>
</ul>

<p><a id="sendability"></a></p>
<h1 id="sendability-and-data-race-protections"><strong>Sendability and Data Race Protections</strong></h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Non-sendable type 'T' used in concurrent actor-isolated context
</code></pre></div></div>

<p>Swift’s concurrency model enforces that types crossing actor boundaries (or used concurrently) must be safe to transfer between threads. A type that isn’t marked as Sendable might introduce data races. This error indicates that you’re using a type in a concurrent context where the compiler cannot guarantee its thread safety.</p>

<p>How to Fix It:</p>

<ul>
  <li>If the type is indeed safe for concurrent use, you can conform it to Sendable.</li>
  <li>Otherwise, consider refactoring to an immutable type or introducing <a href="/apple/mach-o/2024/12/07/Swift-Synchronization-Mechanisms.html">internal synchronization</a> and making it <code class="language-plaintext highlighter-rouge">@unchecked Sendable</code>.</li>
</ul>

<p><a id="actor-isolated-protocol"></a></p>
<h1 id="actor-isolated-protocols"><strong>Actor Isolated Protocols</strong></h1>

<p>I wrote about this in a <a href="/apple/concurrency/2024/12/16/Actor-Conformance-to-Non-Isolated-Protocols.html">previous article</a>.</p>

<p><a id="mainactor"></a></p>
<h1 id="mainactor-isolation-violations"><strong>@MainActor Isolation Violations</strong></h1>

<p>Same as Holy Borla’s <a href="https://github.com/hborla/swift/blob/fdd7402bbfdb5563f0e21c0b3d777bbc2874b3b4/userdocs/diagnostics/actor-isolated-call.md">Calling an actor-isolated method from a synchronous nonisolated context</a></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">Foo</span> <span class="p">{</span>
    <span class="c1">// 💥 Main actor-isolated default value in a nonisolated context</span>
    <span class="k">var</span> <span class="nv">window</span> <span class="o">=</span> <span class="kt">UIWindow</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In Swift 6 view related objects are annotated with @MainActor so you can’t use them outside that isolation context. What follows are variations of this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: call to main actor-isolated initializer 'init()' in a synchronous nonisolated context​
Error: Main actor-isolated property 'foo' cannot be referenced from a nonisolated context
Error: Cannot form key path to main actor-isolated property 'foo'
</code></pre></div></div>

<p>Again, calling an initializer (or method) marked with @MainActor from code that isn’t on the main actor. The solution is to wrap the call in an <code class="language-plaintext highlighter-rouge">await MainActor.run {}</code> or <code class="language-plaintext highlighter-rouge">Task { @MainActor in }</code>. A temporary workaround could be to use <code class="language-plaintext highlighter-rouge">MainActor.assumeIsolated</code> if you are sure such code will only be called from the main actor.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Main actor-isolated property 'foo' cannot be referenced from a Sendable closure.
</code></pre></div></div>

<p>This time we are calling a main isolated property from a Sendable closure. Because it is Sendable, you are going to send that property away from its isolation context.</p>

<p>This error often appears in SwiftUI or other contexts where a closure is marked <code class="language-plaintext highlighter-rouge">@Sendable</code> (implicitly by the system) and you capture a @MainActor state inside it. For instance, using a SwiftUI <code class="language-plaintext highlighter-rouge">.task { ... }</code> modifier (which runs concurrently) and capturing a <code class="language-plaintext highlighter-rouge">@State</code> (main actor) property inside can produce this error.</p>

<p>Several solutions:</p>

<ul>
  <li>
    <p>Mark the closure as running on the main actor, or ensure any UI state is accessed on the main thread. In SwiftUI, you can call <code class="language-plaintext highlighter-rouge">await MainActor.run</code> within the task to update UI state​.</p>
  </li>
  <li>
    <p>Or move the state into a model that is made thread-safe or Sendable. In one case, a developer created a separate view model and declared it <code class="language-plaintext highlighter-rouge">@unchecked Sendable</code> to hold the image data, then used <code class="language-plaintext highlighter-rouge">MainActor.run</code> for UI updates​</p>
  </li>
  <li>
    <p>Or Design your concurrency so that UI-bound data is only touched on the main actor (for example, by marking the entire view model @MainActor or updating SwiftUI <code class="language-plaintext highlighter-rouge">@State</code> within <code class="language-plaintext highlighter-rouge">DispatchQueue.main.async</code> or an actor). In short, avoid capturing main-actor state in detached concurrent closures; perform UI updates on the main actor.</p>
  </li>
</ul>

<p><a id="sendable-errors"></a></p>
<h1 id="sendable-related-errors"><strong>Sendable-Related Errors</strong></h1>

<p><a id="type-conformance"></a></p>
<h3 id="type-conformance">Type Conformance</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Type 'XYZ' does not conform to the 'Sendable' protocol​
</code></pre></div></div>

<p>It means that a type (often a class or a reference type) is being used in a concurrent context but hasn’t been marked as safe to share across threads.</p>

<p>Several solutions:</p>

<ul>
  <li>
    <p>Conform to Sendable: If you know the type is actually safe to use from multiple threads (e.g. it’s immutable or <a href="/apple/mach-o/2024/12/07/Swift-Synchronization-Mechanisms.html">internally synchronized</a>), you can conform to Sendable.</p>
  </li>
  <li>
    <p>Isolate to an actor: If the type isn’t inherently thread-safe, consider isolating it to an actor or marking it @MainActor if it should only be used on the main thread.</p>
  </li>
  <li>
    <p>Redesign usage: In some cases, you might avoid needing the type across threads. For example, rather than capturing a non-Sendable class inside an async task, perform the needed work on that class on its own queue or actor, or copy the necessary data out into a Sendable form (like a struct) to send to the task, or capture it in the closure capture list.</p>
  </li>
</ul>

<p><a id="non-sendable"></a></p>
<h3 id="non-sendable-crossing-boundaries">Non-sendable crossing boundaries</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Passing argument of non-sendable type 'Foo' outside of actor-isolated context may introduce data races
</code></pre></div></div>

<p>This diagnostic appears when you attempt to use a non Sendable value in a way that exits an actor’s protection. In other words, if a type is private to the isolation context of an actor there can’t be data races. But if you send it outside the actor it needs to be Sendable.</p>

<p>Solutions:</p>

<ul>
  <li>Mark the type as Sendable: See elsewhere on this guide how.</li>
  <li>Keep usage on the actor</li>
  <li>Isolate Foo to a global actor: Another strategy is marking the non-sendable type with a global actor (like @MainActor if appropriate). This way, even when used in async calls, it’s still confined to a single actor. For instance, if Foo is only ever used on the main thread, mark it @MainActor – then the call is actor-to-actor and no cross-actor send happens.</li>
</ul>

<p><a id="weak-var"></a></p>
<h3 id="weak-var-is-mutable">Weak var is mutable</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Stored property 'delegate' of 'Sendable'-conforming class 'x' is mutable
</code></pre></div></div>

<p>Delegates need to be mutable because they may change at runtime. This makes your type non Sendable. Using an actor is one way to solve it:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">actor</span> <span class="kt">PipelineDelegateHolder</span> <span class="p">{</span>
    <span class="k">weak</span> <span class="k">var</span> <span class="nv">delegate</span><span class="p">:</span> <span class="kt">PipelineDelegate</span><span class="p">?</span>

    <span class="kd">nonisolated</span> <span class="nf">init</span><span class="p">(</span><span class="nv">delegate</span><span class="p">:</span> <span class="kt">PipelineDelegate</span><span class="p">?)</span> <span class="k">async</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="n">delegate</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">callDelegate</span><span class="p">(</span><span class="n">_</span> <span class="nv">action</span><span class="p">:</span> <span class="p">(</span><span class="kt">PipelineDelegate</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">delegate</span> <span class="o">=</span> <span class="n">delegate</span> <span class="p">{</span>
            <span class="nf">action</span><span class="p">(</span><span class="n">delegate</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="kt">TransPipeline</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">delegateHolder</span><span class="p">:</span> <span class="kt">PipelineDelegateHolder</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">delegate</span><span class="p">:</span> <span class="kt">PipelineDelegate</span><span class="p">)</span> <span class="k">async</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">delegateHolder</span> <span class="o">=</span> <span class="k">await</span> <span class="kt">PipelineDelegateHolder</span><span class="p">(</span><span class="nv">delegate</span><span class="p">:</span> <span class="n">delegate</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Usage</span>
<span class="k">await</span> <span class="n">delegateHolder</span><span class="o">.</span><span class="n">callDelegate</span> <span class="p">{</span> <span class="n">delegate</span> <span class="k">in</span>
    <span class="n">delegate</span><span class="o">.</span><span class="nf">pipeline</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="nv">didUpdateApples</span><span class="p">:</span> <span class="n">apples</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>By using this solution we are adding suspension points in both initializers and delegate calls. Also, the PipelineDelegate needs to be Sendable. If you want a structured concurrency ‘safe’ solution this is it.</p>

<p><a id="sending-value"></a></p>
<h3 id="sending-value-risks-causing-data-races">Sending value risks causing data races</h3>

<p>From Holy Borla’s <a href="https://github.com/hborla/swift/blob/fdd7402bbfdb5563f0e21c0b3d777bbc2874b3b4/userdocs/diagnostics/sending-risks-data-race.md">sending-risks-data-race.md</a>.</p>

<p>The following example calls a non-isolated function from the main actor:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">Person</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>    
    <span class="kd">func</span> <span class="nf">printNameConcurrently</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">@MainActor</span>
<span class="kd">func</span> <span class="nf">onMainActor</span><span class="p">(</span><span class="nv">person</span><span class="p">:</span> <span class="kt">Person</span><span class="p">)</span> <span class="k">async</span> <span class="p">{</span>
    <span class="c1">// calling non-isolated function from the main actor</span>
    <span class="k">await</span> <span class="n">person</span><span class="o">.</span><span class="nf">printNameConcurrently</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Non-sendables (like person) can only be accessed from one isolation domain at a time. Otherwise, who is to say main actor won’t change person while printNameConcurrently() is running?.</p>

<p>Solution:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@execution</span><span class="p">(</span><span class="n">caller</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">printNameConcurrently</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now <code class="language-plaintext highlighter-rouge">printNameConcurrently()</code> runs in the isolation domain of the caller. If called from main, it runs on main.</p>

<p>As of march 2025 this change is not yet implemented. See <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md">Run nonisolated async functions on the caller’s actor by default</a>. So for the time being follow the doctor’s advice <em>if it hurts don’t do it</em>. Instead, make person a Sendable, move it to @MainActor, etc.</p>

<p><a id="sending-closure"></a></p>
<h3 id="sending-closure-risks-causing-data-races">Sending closure risks causing data races</h3>

<p>From Holy Borla’s <a href="https://github.com/hborla/swift/blob/fdd7402bbfdb5563f0e21c0b3d777bbc2874b3b4/userdocs/diagnostics/sending-closure-risks-data-race.md#sending-closure-risks-causing-data-races">sending-closure-risks-data-race.md</a>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">MyModel</span> <span class="p">{</span>
  <span class="k">var</span> <span class="nv">count</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">0</span>

  <span class="kd">func</span> <span class="nf">perform</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">Task</span> <span class="p">{</span>
      <span class="c1">// 💥 error: passing closure as a 'sending' parameter risks causing data races</span>
      <span class="k">self</span><span class="o">.</span><span class="nf">update</span><span class="p">()</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="kd">func</span> <span class="nf">update</span><span class="p">()</span> <span class="p">{</span> <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This code has two isolation domains:</p>
<ul>
  <li>MyModel (nonisolated)</li>
  <li>Task {}</li>
</ul>

<p>Task { self… } contains a reference to self (MyModel) which is now accessible from two domains. Again: non-sendables (like person) can only be accessed from one isolation domain at a time.</p>

<p>Solution</p>
<ul>
  <li>Make MyModel an actor (or isolate to a global one). Actors are Sendable because any code touching them is required to switch to the actor isolation domain. Therefore the actor is only accessible from one (1) isolation domain: its own.</li>
  <li>Another solution is to really send (sending) the value:</li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">MyModel</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">perform</span><span class="p">(</span><span class="nv">model</span><span class="p">:</span> <span class="n">sending</span> <span class="kt">MyModel</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="n">model</span><span class="o">.</span><span class="nf">update</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="kd">func</span> <span class="nf">update</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This works because it enforces single-ownership semantics:</p>
<ul>
  <li>The <code class="language-plaintext highlighter-rouge">model</code> parameter is annotated <code class="language-plaintext highlighter-rouge">sending</code>, meaning: the callsite can’t touch this instance again after <code class="language-plaintext highlighter-rouge">sending</code> it.</li>
  <li>perform is an <code class="language-plaintext highlighter-rouge">static</code> so you know it can’t capture self in the Task closure</li>
</ul>

<p><a id="sending-captures"></a></p>
<h3 id="captures-in-a-sendable-closure">Captures in a @Sendable closure</h3>

<p>From Holy Borla’s <a href="https://github.com/hborla/swift/blob/fdd7402bbfdb5563f0e21c0b3d777bbc2874b3b4/userdocs/diagnostics/sendable-closure-captures.md">sendable-closure-captures.md</a>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">callConcurrently</span><span class="p">(</span>
  <span class="n">_</span> <span class="nv">closure</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="kd">@Sendable</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
<span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>

<span class="kd">func</span> <span class="nf">capture</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">result</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">result</span> <span class="o">+=</span> <span class="mi">1</span>
    
    <span class="n">callConcurrently</span> <span class="p">{</span>
        <span class="c1">// 💥 error: reference to captured var </span>
        <span class="c1">// 'result' in concurrently-executing code</span>
        <span class="nf">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You annotated a closure as Sendable but that closure captures non-Sendable values. This just in: <em>non-sendables can only be accessed from one isolation domain at a time</em>?, news at 5.</p>

<p>Solutions:</p>
<ul>
  <li>If it is a value (struct, enum) copy it in the closure capture list to avoid passing a non-Sendable self.</li>
  <li>Or make self sendable (make it an actor, annotate with global actor, or @unchecked Sendable with internal synchronization)</li>
</ul>

<p><a id="sending-mutex"></a></p>
<h3 id="inout-sending-parameter-cannot-be-task-isolated-at-end-of-function">‘inout sending’ parameter cannot be task-isolated at end of function</h3>

<p>This happens when using Swift 6’s <code class="language-plaintext highlighter-rouge">Mutex</code> type from the Synchronization framework. Example:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">agentsMutex</span><span class="o">.</span><span class="n">withLock</span> <span class="p">{</span> <span class="n">agents</span> <span class="k">in</span>
    <span class="n">agents</span><span class="o">.</span><span class="nf">removeValue</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="kt">AgentRegistryId</span><span class="p">(</span><span class="n">agent</span><span class="p">))</span>
<span class="p">}</span> 
<span class="c1">// 💥 'inout sending' parameter 'agents' </span>
<span class="c1">// cannot be task-isolated at end of function</span>
</code></pre></div></div>

<p>The problem is that <code class="language-plaintext highlighter-rouge">Mutex.withLock</code> method expects a specific signature for its closure:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">withLock</span><span class="p">((</span><span class="k">inout</span> <span class="n">sending</span> <span class="kt">Value</span><span class="p">)</span> <span class="nf">throws</span><span class="p">(</span><span class="kt">E</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">sending</span> <span class="kt">Result</span><span class="p">)</span> <span class="nf">throws</span><span class="p">(</span><span class="kt">E</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">sending</span> <span class="kt">Result</span>
</code></pre></div></div>

<p>The correct way to use the <code class="language-plaintext highlighter-rouge">withLock</code> method is to explicitly mark the parameter as <code class="language-plaintext highlighter-rouge">inout</code> and specify a return type:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">agentsMutex</span><span class="o">.</span><span class="n">withLock</span> <span class="p">{</span> <span class="p">(</span><span class="nv">agents</span><span class="p">:</span> <span class="k">inout</span> <span class="p">[</span><span class="kt">AgentRegistryId</span><span class="p">:</span> <span class="kt">Agent</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="k">in</span>
    <span class="n">agents</span><span class="o">.</span><span class="nf">removeValue</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="kt">AgentRegistryId</span><span class="p">(</span><span class="n">agent</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The error occurs because:</p>

<ol>
  <li>The <code class="language-plaintext highlighter-rouge">withLock</code> method expects a closure that takes an <code class="language-plaintext highlighter-rouge">inout sending</code> parameter</li>
  <li>This parameter is temporarily sent from the Mutex to your closure</li>
  <li>When the closure completes, ownership must be returned to the Mutex</li>
  <li>Without proper <code class="language-plaintext highlighter-rouge">inout</code> annotation, Swift can’t guarantee the thread safety of your mutation</li>
</ol>

<p>The <code class="language-plaintext highlighter-rouge">sending</code> keyword in Swift 6 indicates a value being transferred between isolation domains. When a parameter is marked as <code class="language-plaintext highlighter-rouge">inout sending</code>, it means you’re temporarily taking ownership of a value, but must properly return it when done.</p>

<p>By explicitly marking the parameter as <code class="language-plaintext highlighter-rouge">inout</code> and specifying the return type (often <code class="language-plaintext highlighter-rouge">Void</code>), you’re acknowledging this ownership transfer pattern. I would expect this to be handled by type inference but isn’t, maybe it is intentional (?).</p>

<p><a id="global-state"></a></p>
<h1 id="global-state"><strong>Global state</strong></h1>

<p>Same as Holy Borla’s <a href="https://github.com/hborla/swift/blob/fdd7402bbfdb5563f0e21c0b3d777bbc2874b3b4/userdocs/diagnostics/mutable-global-variable.md">Unsafe mutable global and static variables</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Var 'xxx' is not concurrency-safe because it is non-isolated global shared mutable state
</code></pre></div></div>

<p>This error flags a global variable (or a static) that is mutable and not isolated to any actor. Swift 6 enforces stricter rules for global state​: a global var must either be made immutable or be protected by an actor. If not, it’s considered a potential data race, since any thread could access that global.</p>

<p>The Swift evolution proposal <a href="https://forums.swift.org/t/se-0412-strict-concurrency-for-global-variables/68352">SE-0412 Strict concurrency for global variables</a> details several solutions:</p>

<ul>
  <li>
    <p><strong>Use a global actor</strong>: Mark the global with an actor, commonly @MainActor if it’s related to UI or main thread, or define a custom <code class="language-plaintext highlighter-rouge">@globalActor</code> for it. For example: <code class="language-plaintext highlighter-rouge">@MainActor var myGlobal = 0</code>. This ensures all access to <code class="language-plaintext highlighter-rouge">myGlobal</code> is on the main actor (or your chosen actor), preventing unsynchronized access. Keep in mind you’ll need await to access it from other contexts</p>
  </li>
  <li>
    <p><strong>Make it immutable</strong>: If possible, use let instead of var, and ensure the type is Sendable. An immutable constant of a Sendable type is safe by definition (no races since it never changes)​. This might not be feasible if the value truly needs to change, but sometimes a redesign (e.g., use of a singleton actor to hold the state instead of a global var) can eliminate the need for a mutable global.</p>
  </li>
  <li>
    <p><strong>Mark as nonisolated(unsafe)</strong>: This attribute explicitly opts out of concurrency checking for that variable. For example: <code class="language-plaintext highlighter-rouge">nonisolated(unsafe) var myGlobal = 0</code>. This will silence the error​, but at your own risk. Only do this if you fully understand the implications and perhaps synchronize access.</p>
  </li>
</ul>

<p>An example of this problem is when a SDK protocol requirement conflicts with Structured Concurrency:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ButtonFramePreferenceKey</span><span class="p">:</span> <span class="kt">PreferenceKey</span> <span class="p">{</span>
    <span class="c1">// 💥 Static property 'defaultValue' is not concurrency-safe because it is nonisolated global shared mutable state</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">CGRect</span> <span class="o">=</span> <span class="o">.</span><span class="n">zero</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">reduce</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="k">inout</span> <span class="kt">CGRect</span><span class="p">,</span> <span class="nv">nextValue</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">CGRect</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">value</span> <span class="o">=</span> <span class="nf">nextValue</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I can’t change the SDK so I add <code class="language-plaintext highlighter-rouge">@preconcurrency</code>:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@MainActor</span>
<span class="kd">private</span> <span class="kd">struct</span> <span class="kt">ButtonFramePreferenceKey</span><span class="p">:</span> <span class="kd">@preconcurrency</span> <span class="kt">PreferenceKey</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">CGRect</span> <span class="o">=</span> <span class="o">.</span><span class="n">zero</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">reduce</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="k">inout</span> <span class="kt">CGRect</span><span class="p">,</span> <span class="nv">nextValue</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">CGRect</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">value</span> <span class="o">=</span> <span class="nf">nextValue</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><a id="isolated-value"></a></p>
<h3 id="isolated-value-cannot-be-sent">Isolated value cannot be sent</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Actor isolated value cannot be sent
</code></pre></div></div>

<p>You are sending a non Sendable object through isolation contexts. Make it Sendable.</p>

<p><a id="singleton"></a></p>
<h1 id="singleton-pattern"><strong>Singleton Pattern</strong></h1>

<p>Swift 6’s strict concurrency checking flags singleton patterns as potential data race risks. Here’s a common error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Static property 'shared' is not concurrency-safe because 
       non-'Sendable' type 'YourType' may have shared mutable state
</code></pre></div></div>

<p>This error appears when you have a singleton (static shared instance) of a class that isn’t properly isolated or made <code class="language-plaintext highlighter-rouge">Sendable</code> –obvious fix: make it Sendable. Example:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">UserLanguage</span> <span class="p">{</span>
    <span class="c1">// 💥 Static property 'shared' is not concurrency-safe because </span>
    <span class="c1">// non-'Sendable' type 'UserLanguage' may have shared mutable state</span>
    <span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">UserLanguage</span><span class="p">()</span>
    <span class="kd">private</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note the “<em>may have shared mutable state</em>” even when no mutable state exists. The compiler is overzealous with classes, flagging a potential error for mutable state that may be added in the future.</p>

<h2 id="solutions">Solutions</h2>

<h3 id="actor-based-singleton">Actor-based Singleton</h3>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">actor</span> <span class="kt">UserLanguage</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">UserLanguage</span><span class="p">()</span>

    <span class="k">var</span> <span class="nv">current</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">didSet</span> <span class="p">{</span>
            <span class="kt">UserDefaults</span><span class="o">.</span><span class="n">standard</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="s">"userLanguage"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">savedLanguage</span> <span class="o">=</span> <span class="kt">UserDefaults</span><span class="o">.</span><span class="n">standard</span><span class="o">.</span><span class="nf">string</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="s">"userLanguage"</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">self</span><span class="o">.</span><span class="n">current</span> <span class="o">=</span> <span class="n">savedLanguage</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">let</span> <span class="nv">systemLanguage</span> <span class="o">=</span> <span class="kt">Locale</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="n">language</span><span class="o">.</span><span class="n">languageCode</span><span class="p">?</span><span class="o">.</span><span class="n">identifier</span> <span class="p">??</span> <span class="s">"en"</span>
            <span class="k">self</span><span class="o">.</span><span class="n">current</span> <span class="o">=</span> <span class="n">systemLanguage</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This approach is best when:</p>
<ul>
  <li>You need high-performance thread-safety</li>
  <li>You expect frequent concurrent access from multiple threads</li>
  <li>You’re comfortable with async/await in your codebase</li>
</ul>

<h3 id="mainactor-isolation">MainActor Isolation</h3>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@MainActor</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">UserLanguage</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">UserLanguage</span><span class="p">()</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This approach is best when:</p>
<ul>
  <li>The singleton is primarily used for UI-related tasks</li>
  <li>You want to ensure all access happens on the main thread</li>
  <li>You want to avoid extensive rewriting of synchronous code</li>
</ul>

<h3 id="internally-synchronized-sendable">Internally Synchronized Sendable</h3>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">UserLanguage</span><span class="p">:</span> <span class="kd">@unchecked</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">UserLanguage</span><span class="p">()</span>
    
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">lock</span> <span class="o">=</span> <span class="kt">NSLock</span><span class="p">()</span>
    
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">_current</span><span class="p">:</span> <span class="kt">String</span>
    
    <span class="k">var</span> <span class="nv">current</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span>
            <span class="n">lock</span><span class="o">.</span><span class="nf">lock</span><span class="p">()</span>
            <span class="k">defer</span> <span class="p">{</span> <span class="n">lock</span><span class="o">.</span><span class="nf">unlock</span><span class="p">()</span> <span class="p">}</span>
            <span class="k">return</span> <span class="n">_current</span>
        <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span>
            <span class="n">lock</span><span class="o">.</span><span class="nf">lock</span><span class="p">()</span>
            <span class="k">defer</span> <span class="p">{</span> <span class="n">lock</span><span class="o">.</span><span class="nf">unlock</span><span class="p">()</span> <span class="p">}</span>
            <span class="n">_current</span> <span class="o">=</span> <span class="n">newValue</span>
            <span class="kt">UserDefaults</span><span class="o">.</span><span class="n">standard</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="n">_current</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="s">"userLanguage"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This approach is best when:</p>
<ul>
  <li>You need to maintain synchronous access patterns</li>
  <li>You want to avoid actor isolation or main thread constraints</li>
  <li>You’re willing to implement proper internal synchronization</li>
</ul>

<h2 id="choosing-the-right-approach">Choosing the Right Approach</h2>

<ul>
  <li><strong>Actor</strong>: Best for high-performance concurrency needs</li>
  <li><strong>@MainActor</strong>: Simplest solution for UI-related singletons</li>
  <li><strong>@unchecked Sendable with locks</strong>: Most compatible with existing synchronous code</li>
</ul>

<p>Remember that the key principle is ensuring that shared mutable state can only be accessed from one isolation domain at a time.</p>

<p><a id="concurrency/"></a></p>
<h1 id="concurrency"><strong>Concurrency</strong></h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Instance method 'lock' is unavailable from asynchronous contexts; 
       Use async-safe scoped locking instead
</code></pre></div></div>

<p>You are attempting to await a lock. Example:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">NSLock</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="n">withLock</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">work</span><span class="p">:</span> <span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">)</span> <span class="k">async</span> <span class="k">rethrows</span> <span class="o">-&gt;</span> <span class="kt">T</span> <span class="p">{</span>
        <span class="k">await</span> <span class="nf">lock</span><span class="p">()</span>
        <span class="k">defer</span> <span class="p">{</span> <span class="nf">unlock</span><span class="p">()</span> <span class="p">}</span>
        <span class="k">return</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">work</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Probably because you wanted to avoid actor reentrancy and execute the content of the method atomically.</p>

<p>Consider the case of an audio recorder that stores recordings on disk and properties on Core Data. Maybe you created an async file and Core Data APIs but now you want both to run atomically. For instance, delete on Core Data and then on disk without a concurrent call seeing an intermediate state. How do you do that?</p>

<p>If you have a code path that you want to:</p>
<ul>
  <li><strong>Execute fully</strong> (without reentrant calls): put it inside an actor and don’t spawn additional async calls.</li>
  <li><strong>Execute on main</strong>: annotate it with @MainActor and avoid spawning extra async calls to code not protected by @MainActor –also valid with any global actor.</li>
  <li><strong>Restrict concurrency</strong> using a <a href="https://en.wikipedia.org/wiki/Token_bucket">token bucket</a> like <a href="https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Basics/Concurrency/TokenBucket.swift">TokenBucket.swift</a> or a <a href="https://github.com/groue/Semaphore">AsyncSemaphore</a>.</li>
</ul>

<p>How does it work?</p>
<ul>
  <li>An actor prevents parallel execution (two active threads running the same code concurrently), but not <em>concurrent scheduling</em> (the code can suspend and re-enter).</li>
  <li>@MainActor ensures code runs on main, but async calls to unprotected code <em>may</em> suspend your main-actor code, run the unprotected code on another thread, and then re-enter your main-actor code.</li>
  <li>The token bucket limits the number of concurrent executions like a semaphore does, but instead of blocking, we can suspend and store tasks as continuations <a href="https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Basics/Concurrency/TokenBucket.swift">TokenBucket.swift</a>. An alternative solution is <a href="https://github.com/groue/Semaphore">AsyncSemaphore</a>, that uses a lock inside, but safely and without warnings.</li>
</ul>

<p>I wrote more about the TokenBucket and locks in <a href="/apple/mach-o/2024/12/07/Swift-Synchronization-Mechanisms.html">Swift Synchronization Mechanisms</a>.</p>]]></content><author><name></name></author><category term="apple" /><category term="macos" /><category term="swift" /><category term="apple" /><category term="macos" /><category term="swift" /><summary type="html"><![CDATA[How to solve some Swift 6 migration errors.]]></summary></entry><entry><title type="html">Test Traits</title><link href="https://jano.dev/apple/swift/2025/02/24/Scoping-Traits.html" rel="alternate" type="text/html" title="Test Traits" /><published>2025-02-24T00:00:00+01:00</published><updated>2025-02-24T00:00:00+01:00</updated><id>https://jano.dev/apple/swift/2025/02/24/Scoping-Traits</id><content type="html" xml:base="https://jano.dev/apple/swift/2025/02/24/Scoping-Traits.html"><![CDATA[<h2 id="what-is-a-trait">What is a trait</h2>

<p>A <strong>test trait</strong> in Swift Testing is a modifier you apply to a test or test suite that customizes its behavior.</p>

<p>Example:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">disabled</span><span class="p">(</span><span class="s">"Feature broken"</span><span class="p">))</span>
<span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">tags</span><span class="p">(</span><span class="o">.</span><span class="n">network</span><span class="p">))</span>
<span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">bug</span><span class="p">(</span><span class="s">"IOS-1234"</span><span class="p">,</span> <span class="s">"Crashes on launch"</span><span class="p">))</span>
<span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">enabled</span><span class="p">(</span><span class="nv">if</span><span class="p">:</span> <span class="kt">Server</span><span class="o">.</span><span class="n">isOnline</span><span class="p">))</span>
<span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">timeLimit</span><span class="p">(</span><span class="o">.</span><span class="nf">seconds</span><span class="p">(</span><span class="mi">10</span><span class="p">)))</span>
</code></pre></div></div>

<p>Traits are passed to @Test(…) or @Suite(…) and the framework applies them during execution. In the example above the traits are:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">.disabled(...)</code> — disables the test</li>
  <li><code class="language-plaintext highlighter-rouge">.tags(...)</code> — groups or filters tests</li>
  <li><code class="language-plaintext highlighter-rouge">.bug(...)</code> — links the test to a ticket</li>
  <li><code class="language-plaintext highlighter-rouge">.enabled(if:)</code> — conditionally runs the test</li>
  <li><code class="language-plaintext highlighter-rouge">.timeLimit(...)</code> — sets a time cap</li>
</ul>

<h2 id="trait-protocols">Trait protocols</h2>

<p>Formally, a trait is any type that conforms to the <a href="https://developer.apple.com/documentation/testing/testtrait">TestTrait</a> protocol. It can:</p>

<ul>
  <li>Add metadata (name, bug ID, tags)</li>
  <li>Modify execution (disable, enable, retry, set timeouts)</li>
  <li>Provide setup/teardown logic (via TestScoping)</li>
  <li>Interact with runtime conditions (ConditionTrait)</li>
</ul>

<p>There are three traits protocols</p>

<ul>
  <li><a href="https://developer.apple.com/documentation/testing/testtrait">TestTrait</a>: the base protocol</li>
  <li><a href="https://developer.apple.com/documentation/testing/testscoping">TestScoping</a>: extends TestTrait and allows running code before and after a test.</li>
  <li><a href="https://developer.apple.com/documentation/testing/conditiontrait">ConditionTrait</a>: extends TestTrait and is used to conditionally include or skip tests.</li>
</ul>

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

<span class="kd">protocol</span> <span class="kt">ConditionTrait</span><span class="p">:</span> <span class="kt">TestTrait</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">evaluate</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Bool</span>
<span class="p">}</span>

<span class="kd">protocol</span> <span class="kt">TestScoping</span><span class="p">:</span> <span class="kt">TestTrait</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">provideScope</span><span class="p">(</span>
    <span class="k">for</span> <span class="nv">test</span><span class="p">:</span> <span class="kt">Test</span><span class="p">,</span>
    <span class="nv">testCase</span><span class="p">:</span> <span class="kt">Test</span><span class="o">.</span><span class="kt">Case</span><span class="p">?,</span>
    <span class="n">performing</span> <span class="nv">function</span><span class="p">:</span> <span class="kd">@Sendable</span> <span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
  <span class="p">)</span> <span class="k">async</span> <span class="k">throws</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="conditiontrait">ConditionTrait</h2>

<p><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0010-evaluate-condition.md">0010 valuate ConditionTrait</a></p>

<p>A <strong>test trait</strong> in Swift Testing is a modifier you apply to a test or test suite that customizes its behavior.</p>

<p>A <code class="language-plaintext highlighter-rouge">ConditionTrait</code> is a kind of test trait in Swift Testing that evaluates to a boolean and is used to decide whether to run a test or not. The following modifiers <code class="language-plaintext highlighter-rouge">.enabled(if:)</code> and <code class="language-plaintext highlighter-rouge">.disabled(...)</code> are condition traits:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">enabled</span><span class="p">(</span><span class="nv">if</span><span class="p">:</span> <span class="kt">Server</span><span class="o">.</span><span class="n">isOnline</span><span class="p">))</span>
<span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="nf">disabled</span><span class="p">(</span><span class="s">"Currently broken"</span><span class="p">))</span>
</code></pre></div></div>

<p>Swift 6.2 added an evaluate() method to ConditionTrait to evaluate the condition outside Test instances:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">skipOnCI</span> <span class="o">=</span> <span class="k">#condition</span><span class="p">(</span><span class="kt">Skip</span><span class="p">(</span><span class="nv">if</span><span class="p">:</span> <span class="p">{</span> <span class="nf">isRunningOnCI</span><span class="p">()</span> <span class="p">}))</span>
<span class="k">if</span> <span class="k">try</span> <span class="k">await</span> <span class="n">skipOnCI</span><span class="o">.</span><span class="nf">evaluate</span><span class="p">()</span> <span class="p">{</span>
  <span class="nf">print</span><span class="p">(</span><span class="s">"Skipping test setup because we're on CI"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  <span class="nf">startExpensiveSetup</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can call evaluate on any ConditionTrait:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Skip(if:)</code></li>
  <li><code class="language-plaintext highlighter-rouge">Run(if:)</code></li>
  <li>Any custom type conforming to ConditionTrait</li>
</ul>

<h2 id="testscoping">TestScoping</h2>

<p><a href="https://developer.apple.com/documentation/testing/testscoping">TestScoping</a> specifies code that runs before and after the annotated test or suite. It can:</p>

<ul>
  <li>Set up resources before the test runs</li>
  <li>Use @TaskLocal to inject scoped values</li>
  <li>Clean up after the test</li>
</ul>

<p>It’s the Structured Concurrency replacement for XCTest <code class="language-plaintext highlighter-rouge">setUp</code>/<code class="language-plaintext highlighter-rouge">tearDown</code> but better:</p>

<ul>
  <li>A trait can be reused across test suites.</li>
  <li>A trait can be applied to only specific tests.</li>
  <li>There is no global mutable state that may interfere with test parallelization.</li>
  <li>It uses Structured Concurrency features for increased performance and compatibility.</li>
</ul>

<h3 id="example">Example</h3>

<p>I want my tests to access an instance that determines what database to connect to:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">DatabaseContext</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">connectionString</span><span class="p">:</span> <span class="kt">String</span>
    <span class="k">let</span> <span class="nv">isTestEnvironment</span><span class="p">:</span> <span class="kt">Bool</span>
<span class="p">}</span></code></pre></figure>

<p>Here is how I do it:</p>
<ol>
<li>Add a <a href="/programming/2021/10/29/structured-concurrency.html#tasklocal">TaskLocal</a> variable –<i>this is the Structured Concurrency version of a thread-local value.</i>
<p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">DatabaseContext</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="kd">@TaskLocal</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">current</span><span class="p">:</span> <span class="kt">DatabaseContext</span><span class="p">?</span>
<span class="p">}</span></code></pre></figure>

</p>
</li>

<li>Create a <code>TestTrait</code> conforming to <code>TestScoping</code>. This protocol has only one function, intended to set task local values when the test runs. The parameter <code>function</code> is in charge of running the test. You can ignore the other parameters.
<p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">DatabaseContextTrait</span><span class="p">:</span> <span class="kt">TestTrait</span><span class="p">,</span> <span class="kt">TestScoping</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">provideScope</span><span class="p">(</span>
        <span class="k">for</span> <span class="nv">test</span><span class="p">:</span> <span class="kt">Test</span><span class="p">,</span>
        <span class="nv">testCase</span><span class="p">:</span> <span class="kt">Test</span><span class="o">.</span><span class="kt">Case</span><span class="p">?,</span>
        <span class="n">performing</span> <span class="nv">function</span><span class="p">:</span> <span class="kd">@Sendable</span> <span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
    <span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>

        <span class="c1">// this is the instance we are about to set as task local value</span>
        <span class="k">let</span> <span class="nv">testContext</span> <span class="o">=</span> <span class="kt">DatabaseContext</span><span class="p">(</span>
            <span class="nv">connectionString</span><span class="p">:</span> <span class="s">"memory:test-db"</span><span class="p">,</span>
            <span class="nv">isTestEnvironment</span><span class="p">:</span> <span class="kc">true</span>
        <span class="p">)</span>

        <span class="c1">// next line sets the value as task local</span>
        <span class="k">try</span> <span class="k">await</span> <span class="kt">DatabaseContext</span><span class="o">.</span><span class="n">$current</span><span class="o">.</span><span class="nf">withValue</span><span class="p">(</span><span class="n">testContext</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// ... this line runs before the test</span>
            <span class="k">try</span> <span class="k">await</span> <span class="nf">function</span><span class="p">()</span> <span class="c1">// this function runs the test itself</span>
            <span class="c1">// ... this line runs after the test</span>
        <span class="p">}</span>

        <span class="c1">// Warning: the task value is released after the function()</span>
        <span class="c1">// runs so it’s not available for code running after the test</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Make the trait available as a static property</span>
<span class="kd">extension</span> <span class="kt">Trait</span> <span class="k">where</span> <span class="k">Self</span> <span class="o">==</span> <span class="kt">DatabaseContextTrait</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">databaseContext</span><span class="p">:</span> <span class="k">Self</span> <span class="p">{</span> <span class="kt">Self</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

</p>
</li>

<li>Add the trait to a test and read the thread local value.
<p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="n">databaseContext</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">testDatabaseOperations</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="c1">// Access the thread local value</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">context</span> <span class="o">=</span> <span class="kt">DatabaseContext</span><span class="o">.</span><span class="n">current</span> <span class="k">else</span> <span class="p">{</span>
        <span class="kt">Issue</span><span class="o">.</span><span class="nf">record</span><span class="p">(</span><span class="s">"No database context available"</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>

    <span class="c1">// ... perform operations with it.</span>
    <span class="c1">// for instance, let’s check its properties</span>

    <span class="cp">#expect(context.isTestEnvironment)</span>
    <span class="cp">#expect(context.connectionString == "memory:test-db")</span>
<span class="p">}</span></code></pre></figure>

</p>
</li>
</ol>

<h3 id="suite-behavior-and-test-case-behavior">Suite Behavior and Test Case Behavior</h3>

<p>The proposal intelligently handles how traits are applied at different levels:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Apply to an entire suite</span>
<span class="kd">@Suite</span><span class="p">(</span><span class="o">.</span><span class="n">myTrait</span><span class="p">)</span>
<span class="kd">struct</span> <span class="kt">DatabaseTests</span> <span class="p">{</span>
  <span class="kd">@Test</span> <span class="kd">func</span> <span class="nf">test1</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
  <span class="kd">@Test</span> <span class="kd">func</span> <span class="nf">test2</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Apply only to a specific test</span>
<span class="kd">@Test</span><span class="p">(</span><span class="o">.</span><span class="n">myOtherTrait</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">specificTest</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span></code></pre></figure>

<p>By default, traits use different behavior depending on context:</p>
<ul>
  <li>Suite traits that are non-recursive provide their scope once for the entire suite</li>
  <li>Suite traits that are recursive provide their scope once for each test function</li>
  <li>Test function traits provide their scope once for each test case (useful for parameterized tests)</li>
</ul>

<p>If you plan to apply a trait to an entire suite <strong>and</strong> each test in that suite, mark the trait as <code class="language-plaintext highlighter-rouge">recursive</code>. Otherwise, it might only run once for the whole suite. This simple oversight can cause unexpected test behaviors.</p>

<h3 id="implementation-details">Implementation Details</h3>

<p>Under the hood, this feature works through:</p>

<ol>
  <li>The <code class="language-plaintext highlighter-rouge">TestScoping</code> protocol with a required <code class="language-plaintext highlighter-rouge">provideScope</code> method that wraps test execution</li>
  <li>A <code class="language-plaintext highlighter-rouge">scopeProvider</code> method on the <code class="language-plaintext highlighter-rouge">Trait</code> protocol that returns an optional <code class="language-plaintext highlighter-rouge">TestScoping</code> instance</li>
  <li>Default implementations that handle most common cases automatically</li>
</ol>

<p>This design cleverly avoids creating unnecessarily deep call stacks when multiple traits are applied to the same test, as only traits that actually need custom behavior participate in the execution chain.</p>

<h3 id="compared-to-xctest">Compared to XCTest</h3>

<p>In short, Test Scoping Traits let you customize test behavior using Swift concurrency, without resorting to global state. It’s another step toward more composable, precise, and inherently safe testing in Swift. For more information, see the testing proposal <a href="https://github.com/swiftlang/swift-testing/blob/main/Documentation/Proposals/0007-test-scoping-traits.md">SWT-0007 Test Scoping Traits</a>.</p>]]></content><author><name></name></author><category term="apple" /><category term="swift" /><summary type="html"><![CDATA[Test Scoping in the Apple Testing framework.]]></summary></entry><entry><title type="html">Accessibility Permission in macOS</title><link href="https://jano.dev/apple/macos/swift/2025/01/08/Accessibility-Permission.html" rel="alternate" type="text/html" title="Accessibility Permission in macOS" /><published>2025-01-08T03:03:03+01:00</published><updated>2025-01-08T03:03:03+01:00</updated><id>https://jano.dev/apple/macos/swift/2025/01/08/Accessibility-Permission</id><content type="html" xml:base="https://jano.dev/apple/macos/swift/2025/01/08/Accessibility-Permission.html"><![CDATA[<h3 id="macos-security-overview">macOS Security Overview</h3>

<!-- Introductory text -->
<p>Apple provides multiple security mechanisms in macOS that rely on each other.</p>

<p><a href="/images/apple-security.svg" title="Opens in new window" target="_blank">
  <img src="/images/apple-security.svg" />
</a>
<br /><br /></p>

<!-- ========== App Execution Security ========== -->

<p><b>App Execution Security</b></p>
<ul>
    <li>
      <span id="more3" title="hint3" anchorid="3">App Sandbox</span>:
      isolates an app’s activities to limit potential damage.
    </li>
  <li>
    <span id="more1" title="hint1" anchorid="1">Gatekeeper</span>:
    ensures only trusted apps can run.
  </li>
  <li>
    <span id="more2" title="hint2" anchorid="2">Notarization &amp; Developer ID</span>:
    verifies software integrity and developer legitimacy.
  </li>
</ul>

<div id="hint1" class="aside">
  <h4>Gatekeeper</h4>
  <div class="aside-text">
    Gatekeeper controls which applications can be executed in macOS by:
    <ul>
      <li>Checking for a valid Developer ID signature from Apple, so that malicious developers can be revoked if necessary.</li>
      <li>Requiring apps to pass Apple’s notarization process, which scans software for suspicious content and security vulnerabilities.</li>
    </ul>
  </div>
</div>

<div id="hint2" class="aside">
  <h4>Notarization &amp; Developer ID</h4>
  <div class="aside-text">
    Notarization &amp; Developer ID work together to validate the origin and safety of an app:
    <ul>
      <li>Developer ID: A unique identifier Apple provides to trusted developers, helping users recognize legitimate software sources.</li>
      <li>Notarization: A process where Apple’s automated systems scan and approve apps, identifying malicious code or security flaws before they reach your Mac.</li>
    </ul>
  </div>
</div>

<div id="hint3" class="aside">
  <h4>App Sandbox</h4>
  <div class="aside-text">
    The App Sandbox confines each application to a restricted environment:
    <ul>
      <li>Prevents an app from freely reading or modifying files outside its container.</li>
      <li>Limits network access and system-level changes to minimize damage if the app is compromised.</li>
    </ul>
  </div>
</div>

<!-- ========== System-Level Security ========== -->

<p><b>System-Level Security</b></p>
<ul>
    <li>
      <span id="more6" title="hint6" anchorid="6">Secure Enclave</span>:
      provides hardware-level cryptographic security.
    </li>
  <li>
    <span id="more4" title="hint4" anchorid="4">SIP (System Integrity Protection)</span>:
    protects critical system files.
  </li>
  <li>
    <span id="more5" title="hint5" anchorid="5">XProtect</span>:
    built-in anti-malware that scans for known threats.
  </li>
</ul>

<div id="hint4" class="aside">
  <h4>SIP (System Integrity Protection)</h4>
  <div class="aside-text">
    SIP defends against unauthorized modifications to core macOS components:
    <ul>
      <li>Restricts the root user from altering protected files and folders.</li>
      <li>Ensures only Apple-signed processes can make system-level changes.</li>
    </ul>
  </div>
</div>

<div id="hint5" class="aside">
  <h4>XProtect</h4>
  <div class="aside-text">
    XProtect is Apple’s built-in anti-malware solution:
    <ul>
      <li>Uses Apple-updated malware definitions to detect known threats.</li>
      <li>Operates behind the scenes, automatically blocking harmful software.</li>
    </ul>
  </div>
</div>

<div id="hint6" class="aside">
  <h4>Secure Enclave</h4>
  <div class="aside-text">
    The Secure Enclave is a dedicated coprocessor that:
    <ul>
      <li>Manages sensitive data like biometric information (e.g., Touch ID).</li>
      <li>Performs cryptographic operations in a secure, hardware-isolated environment.</li>
    </ul>
  </div>
</div>

<!-- ========== Data & Privacy Protection ========== -->

<p><b>Data &amp; Privacy Protection</b></p>
<ul>
  <li>
    <span id="more8" title="hint8" anchorid="8">FileVault</span>:
    provides full-disk encryption to protect your files.
  </li>
  <li>
    <span id="more9" title="hint9" anchorid="9">Keychain</span>:
    stores passwords and credentials securely.
  </li>
  <li>
    <span id="more7" title="hint7" anchorid="7">TCC (Transparency, Consent, &amp; Control)</span>:
    manages permissions for sensitive data.
  </li>
</ul>

<div id="hint7" class="aside">
  <h4>TCC (Transparency, Consent, &amp; Control)</h4>
  <div class="aside-text">
    TCC governs app requests to sensitive resources:
    <ul>
      <li>Prompts for consent when an app wants to access things like your camera, microphone, or contacts.</li>
      <li>Allows you to review and adjust permissions in System Settings at any time.</li>
    </ul>
  </div>
</div>

<div id="hint8" class="aside">
  <h4>FileVault</h4>
  <div class="aside-text">
    FileVault encrypts your disk to protect data at rest:
    <ul>
      <li>Uses XTS-AES-128 encryption to secure the entire startup volume.</li>
      <li>Helps prevent unauthorized access if your Mac is lost or stolen.</li>
    </ul>
  </div>
</div>

<div id="hint9" class="aside">
  <h4>Keychain</h4>
  <div class="aside-text">
    Keychain securely stores credentials and secrets:
    <ul>
      <li>Encrypts your passwords, certificates, and private keys.</li>
      <li>Relies on the system’s security frameworks to prevent unauthorized access.</li>
    </ul>
  </div>
</div>

<h3 id="the-accessibility-permission">The Accessibility Permission</h3>

<p>Accessibility permission falls under the TCC framework. It opens your system to programmatic control for <span id="more14" title="hint14" anchorid="14">certain purposes</span>.</p>
<div id="hint14" class="aside">
  <h4>Accessibility Examples</h4>
  <div class="aside-text">
    Examples of applications that need accessibility:
    <ul>
      <li>Automation utilities</li>
      <li>Window management tools</li>
      <li>Voice control applications</li>
      <li>Screen readers for visually impaired users</li>
    </ul>
    Examples of application control:
    <ul>
      <li>Simulated keyboard and mouse input</li>
      <li>Monitor user interactions with other applications</li>
      <li>Access and manipulate content from windows and controls across applications</li>
    </ul>
  </div>
</div>

<p>Applications with accessibility permission are also referred to as <b>trusted</b>. macOS requires explicit user permission to designate an app as trusted. This takes the form of a dialog that requires admin authentication, and a Settings panel where permissions can be revoked. Spoiler alert: there is no clean way around manual authentication.</p>

<h3 id="configuration">Configuration</h3>

<p>To be able to request accessibility permissions you need to</p>
<ul>
  <li>Declare an entitlement if you run on a hardened runtime.</li>
  <li>Declare the reason you need accessibility in your Info.plist.</li>
  <li>Include a provisioning profile if you plan to distribute in the App Store.</li>
</ul>

<p>In the entitlements file:</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;key&gt;</span>com.apple.security.accessibility<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;true/&gt;</span></code></pre></figure>

<p>In the Info.plist:</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;key&gt;</span>NSAccessibilityUsageDescription<span class="nt">&lt;/key&gt;</span>
<span class="nt">&lt;string&gt;</span>Accessibility permissions are used for window management.<span class="nt">&lt;/string&gt;</span></code></pre></figure>

<h3 id="request-accessibility">Request Accessibility</h3>

<p>To find out if the application has permissions:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Returns TRUE if the current process is a trusted accessibility client</span>
<span class="k">let</span> <span class="nv">isGranted</span> <span class="o">=</span> <span class="kt">AXIsProcessTrusted</span><span class="p">()</span></code></pre></figure>

<p>To prompt the user for permissions:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">options</span><span class="p">:</span> <span class="kt">NSDictionary</span> <span class="o">=</span> <span class="p">[</span>
    <span class="n">kAXTrustedCheckOptionPrompt</span><span class="o">.</span><span class="nf">takeUnretainedValue</span><span class="p">()</span> <span class="k">as</span> <span class="kt">String</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">]</span>
<span class="kt">AXIsProcessTrustedWithOptions</span><span class="p">(</span><span class="n">options</span><span class="p">)</span></code></pre></figure>

<p>The code above will either present a system dialog, or redirect the user to the Accessibility panel at <i>Settings &gt; Privacy &amp; Security &gt; Accessibility</i>. Such panel can also be open programmatically with:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> 
<span class="s">"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="kt">NSWorkspace</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">open</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<h3 id="grant-accessibility">Grant accessibility</h3>

<p>A workflow that is reliable is to</p>
<ol>
  <li>Build the application</li>
  <li>Remove and add back the application to Accessibility.</li>
  <li>Run the application from Xcode &gt; Product &gt; Perform Action &gt; Run Without Building</li>
</ol>

<p>You will be able to debug the application, but it is of course, very inconvenient to go through all this after each build.</p>

<p>TCC provides a utility (<span id="more13" title="hint13" anchorid="13">tccutil</span>) that can reset permissions, but not assign them. The <span id="more10" title="hint10" anchorid="10">TCC database</span> is a SQLite file on disk you can read, but writing requires <span id="more15" title="hint15" anchorid="15">disabling SIP</span>, which opens the possibility to make a catastrophic mistake that damages your system.</p>

<div id="hint15" class="aside">
  <h4>Disabling SIP</h4>
  <div class="aside-text">
    <ol>
        <li>Turn on the Mac and press ⌘R until you see the Apple logo or the recovery utilities.</li>
        <li>Go to the Utilities menu at the top and select Terminal.</li>
        <li>Type: <code>csrutil disable</code></li>
        <li>Restart the Mac.</li>
    </ol>
    Once restarted type <code>crsutil status</code> to verify it is disabled. <br />To re-enable SIP repeat the steps above but run <code>csrutil enable</code>.
  </div>
</div>

<div id="hint13" class="aside">
  <h4>TCC.db</h4>
  <div class="aside-text">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># clears accessibility for one app</span>
<span class="nb">sudo </span>tccutil reset Accessibility <span class="nv">$PRODUCT_BUNDLE_IDENTIFIER</span>

<span class="c"># clears accessibility for all apps</span>
tccutil reset Accessibility</code></pre></figure>

  </div>
</div>

<div id="hint10" class="aside">
<h4>TCC.db</h4>
<div class="aside-text">

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># is my app authorized? 1=granted, 2=denied</span>
<span class="nb">sudo </span>sqlite3 <span class="s2">"/Library/Application Support/com.apple.TCC/TCC.db"</span> <span class="s2">"SELECT auth_value FROM access WHERE service='kTCCServiceAccessibility' AND client='dev.jano.orange';"</span>

<span class="c"># read row</span>
<span class="nb">sudo </span>sqlite3 <span class="s2">"/Library/Application Support/com.apple.TCC/TCC.db"</span> <span class="s2">"SELECT * FROM access WHERE service='kTCCServiceAccessibility' AND client='dev.jano.orange';"</span>

<span class="c"># write (if SIP is disabled)</span>
<span class="nb">sudo </span>sqlite3 <span class="s2">"/Library/Application Support/com.apple.TCC/TCC.db"</span> <span class="s2">"UPDATE access SET auth_value = 1 WHERE service = 'kTCCServiceAccessibility' AND client = 'dev.jano.orange';"</span>

<span class="c"># list clients allowed in my machine</span>
<span class="nb">sudo </span>sqlite3 <span class="s2">"/Library/Application Support/com.apple.TCC/TCC.db"</span> <span class="s2">"SELECT client FROM access WHERE service='kTCCServiceAccessibility';"</span>

<span class="c"># show all services tracked there</span>
<span class="nb">sudo </span>sqlite3 <span class="s2">"/Library/Application Support/com.apple.TCC/TCC.db"</span> <span class="s2">"SELECT DISTINCT service FROM access;"</span></code></pre></figure>


    <p>The <code>service</code>, <code>client</code>, and <code>auth_value</code> are well known. The others are not documented, but more or less this is the unofficial table structure</p>
    <table>
      <tr>
        <th>Column</th>
        <th>Example Value</th>
        <th>Description</th>
      </tr>
      <tr>
        <td class="column-name"><b>service</b></td>
        <td>kTCCServiceAccessibility</td>
        <td>The type of permission</td>
      </tr>
      <tr>
        <td class="column-name"><b>client</b></td>
        <td>dev.jano.orange</td>
        <td>The bundle identifier</td>
      </tr>
      <tr>
        <td class="column-name">client_type</td>
        <td>0</td>
        <td>0 typically means bundled app</td>
      </tr>
      <tr>
        <td class="column-name"><b>auth_value</b></td>
        <td>2</td>
        <td>2 usually indicates denied, 1 is allowed</td>
      </tr>
      <tr>
        <td class="column-name">auth_reason</td>
        <td>4</td>
        <td>Reason code for the authorization</td>
      </tr>
      <tr>
        <td class="column-name">auth_version</td>
        <td>1</td>
        <td>Version of the authorization</td>
      </tr>
      <tr>
        <td class="column-name">csreq</td>
        <td>??</td>
        <td>Code signing requirement</td>
      </tr>
      <tr>
        <td class="column-name">policy_id</td>
        <td>null</td>
        <td>Policy identifier</td>
      </tr>
      <tr>
        <td class="column-name">indirect_object_identifier</td>
        <td>0</td>
        <td>Used for file/folder access permissions</td>
      </tr>
      <tr>
        <td class="column-name">flags</td>
        <td>UNUSED</td>
        <td>Flag settings</td>
      </tr>
      <tr>
        <td class="column-name">last_modified</td>
        <td>null</td>
        <td>Last modification timestamp</td>
      </tr>
      <tr>
        <td class="column-name">pid</td>
        <td>0</td>
        <td>Process ID</td>
      </tr>
      <tr>
        <td class="column-name">expired_at</td>
        <td>1736658020</td>
        <td>Unix timestamp when permission expires</td>
      </tr>
      <tr>
        <td class="column-name">remote_pid</td>
        <td>null</td>
        <td>Remote process ID</td>
      </tr>
      <tr>
        <td class="column-name">responsibility_id</td>
        <td>UNUSED</td>
        <td>Responsibility identifier</td>
      </tr>
      <tr>
        <td class="column-name">temporary_grant</td>
        <td>0</td>
        <td>Whether this is a temporary permission</td>
      </tr>
    </table>
    <p>There is an utility in GitHub: <a href="https://github.com/carlashley/tccprofile">tccprofile</a> but didn’t look into it. There is another local database at <code>~/Library/Application\ Support/com.apple.TCC/TCC.db</code> but it is not relevant.</p>
  </div>
</div>

<h3 id="recommendations">Recommendations</h3>

<p><i>(aka things to do to avoid problems)</i></p>

<p><b>Sign the application</b> to make its identity clear to macOS. What I read online is that each build changes the app signature so permissions have to be requested again. But what I experienced is that signing with a Developer ID certificate is enough for the system to recognize it is the same app.</p>

<p>To sign your app</p>
<ol>
<li>Go to Signing &amp; Capabilities</li>
<li>Set a Team</li>
<li>There are several options here. I use a Developer ID certificate and a Developer ID profile tied to it.</li>
<li>Once signed, you can check the state of your app with <span id="more11" title="hint11" anchorid="11">codesign</span>.</li>
</ol>

<div id="hint11" class="aside">
  <h4>codesign</h4>
  <div class="aside-text">
<p>Verifying the application is signed.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">% codesign <span class="nt">-d</span> <span class="nt">-vv</span> OrangeApp.app

<span class="nv">Executable</span><span class="o">=</span>Foo/Build/Debug/OrangeApp.app/Contents/MacOS/OrangeApp
<span class="nv">Identifier</span><span class="o">=</span>dev.jano.orangeapp
<span class="nv">Format</span><span class="o">=</span>app bundle with Mach-O thin <span class="o">(</span>arm64<span class="o">)</span>
CodeDirectory <span class="nv">v</span><span class="o">=</span>20400 <span class="nv">size</span><span class="o">=</span>650 <span class="nv">hashes</span><span class="o">=</span>10+6 <span class="nv">location</span><span class="o">=</span>embedded
Signature <span class="nv">size</span><span class="o">=</span>4691
<span class="nv">Authority</span><span class="o">=</span>Apple Development: Ellie Williams <span class="o">(</span>H6L34F7G12<span class="o">)</span> 👈
<span class="nv">Authority</span><span class="o">=</span>Apple Worldwide Developer Relations Certification Authority
<span class="nv">Authority</span><span class="o">=</span>Apple Root CA
Signed <span class="nv">Time</span><span class="o">=</span>Jan 12, 2025 at 09:10:19 👈
Info.plist <span class="nv">entries</span><span class="o">=</span>28
<span class="nv">TeamIdentifier</span><span class="o">=</span>PPSA6C3P8Q 👈
Sealed Resources <span class="nv">version</span><span class="o">=</span>2 <span class="nv">rules</span><span class="o">=</span>13 <span class="nv">files</span><span class="o">=</span>2
Internal requirements <span class="nv">count</span><span class="o">=</span>1 <span class="nv">size</span><span class="o">=</span>183</code></pre></figure>

  </div>
</div>

<p><b>Copy the product to a stable location</b>. What I read is that the default location of <code class="language-plaintext highlighter-rouge">~/Library/Developer/Xcode/DerivedData/</code> is hidden and changes between builds, which may confuse the system. But what I experienced is that the system recognizes the application regardless where it is generated.</p>

<p>FYI: changing the destination folder may cause problems if one of your dependencies declares a localization bundle of its own. If this is your case, you can still work around it and change the destination folder.</p>

<p>There are three ways to declare a custom location:</p>
<ul>
  <li>Set a <span id="more12" title="hint12" anchorid="12">build property</span>.</li>
  <li>Xcode &gt; project name &gt; Build Settings &gt; Build Location &gt; Per-configuration Build Products Path.</li>
  <li>Product &gt; Scheme &gt; Edit Scheme… &gt; Run &gt; Build Configuration &gt; Debug &gt; Options &gt; Build Location &gt; Custom</li>
</ul>

<div id="hint12" class="aside">
  <h4>Build property</h4>
  <div class="aside-text">
<p>This will place the product in <code>Build/Debug/YourApp.app</code></p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">CONFIGURATION_BUILD_DIR <span class="o">=</span>
<span class="si">$(</span>PROJECT_DIR<span class="si">)</span>/Build/<span class="si">$(</span>CONFIGURATION<span class="si">)$(</span>EFFECTIVE_PLATFORM_NAME<span class="si">)</span></code></pre></figure>

  </div>
</div>

<h3 id="troubleshooting">Troubleshooting</h3>

<p>The Console.app is your friend –despite its unfriendly UI. You can spot the Accesibility permission being denied if you paste the following in the search field (top, right):</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">type</span>:error
subsystem:com.apple.sandbox.reporting
category:violation</code></pre></figure>

<p>The error will say something like</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">Sandbox: OrangeApp<span class="o">(</span>16745<span class="o">)</span> deny<span class="o">(</span>1<span class="o">)</span> mach-lookup com.apple.universalaccessAuthWarn</code></pre></figure>

<p>Another way to keep an eye on the console is to stream from the terminal:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">log stream <span class="nt">--predicate</span> <span class="se">\</span>
<span class="s1">'process == "tccd" OR process == "OrangeApp" OR process="sandboxd"'</span></code></pre></figure>]]></content><author><name></name></author><category term="apple" /><category term="macos" /><category term="swift" /><category term="apple" /><category term="macos" /><category term="swift" /><summary type="html"><![CDATA[Granting accessibility permission on macOS applications.]]></summary></entry><entry><title type="html">A minimal Core Data Stack</title><link href="https://jano.dev/apple/coredata/2024/12/26/Core-Data-Stack.html" rel="alternate" type="text/html" title="A minimal Core Data Stack" /><published>2024-12-26T03:03:03+01:00</published><updated>2024-12-26T03:03:03+01:00</updated><id>https://jano.dev/apple/coredata/2024/12/26/Core-Data-Stack</id><content type="html" xml:base="https://jano.dev/apple/coredata/2024/12/26/Core-Data-Stack.html"><![CDATA[<p>Let’s build a type-safe Core Data stack that’s both powerful and pleasant to use. We’ll add features incrementally, starting with fetching, and adding declarative queries and domain separation.</p>

<p>This will be the result:</p>

<div class="aside-text">

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">UserEntity</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span>   <span class="c1">// fetch entities</span>
<span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">User</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="o">.</span><span class="nf">id</span><span class="p">(</span><span class="s">"1"</span><span class="p">))</span> <span class="c1">// fetch domain objects</span>

<span class="c1">// declarative queries with fluent API</span>
<span class="k">let</span> <span class="nv">query</span> <span class="o">=</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">UserEntity</span><span class="o">&gt;</span>
        <span class="o">.</span><span class="n">all</span>
        <span class="o">.</span><span class="nf">ascending</span><span class="p">(</span><span class="s">"lastName"</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">ascending</span><span class="p">(</span><span class="s">"firstName"</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">and</span><span class="p">(</span><span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"age &gt; %d"</span><span class="p">,</span> <span class="mi">18</span><span class="p">))</span>
        <span class="o">.</span><span class="nf">and</span><span class="p">(</span><span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"isActive == YES"</span><span class="p">))</span>
        <span class="o">.</span><span class="nf">limit</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">users</span> <span class="o">=</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">query</span><span class="p">)</span></code></pre></figure>

  </div>

<h2 id="fetching-entities">Fetching Entities</h2>

<p>This is the simplest API possible without macros or property wrappers. Can you guess the <span id="more1" title="hint1" anchorid="1">implementation</span>?</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">UserEntity</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span></code></pre></figure>

<div id="hint1" class="aside">
  <h4>fetch() implementation</h4>
  <div class="aside-text">
<p>This is how you fetch a specific entity in Core Data:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">fetchRequest</span> <span class="o">=</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Person</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="s">"Person"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">persons</span> <span class="o">=</span> <span class="k">try</span> <span class="n">persistentContainer</span><span class="o">.</span><span class="n">viewContext</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">fetchRequest</span><span class="p">)</span></code></pre></figure>


<p>It works for any type so we can make it generic. The name should match the class name.</p>


<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">NSManagedObject</span> <span class="p">{</span>
    <span class="kd">class</span> <span class="kd">func</span> <span class="nf">entityName</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="kt">String</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="k">self</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">NSPersistentContainer</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="n">fetch</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="o">&gt;</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">T</span><span class="p">]</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">viewContext</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span>
            <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="kt">T</span><span class="o">.</span><span class="nf">entityName</span><span class="p">())</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>


<p>And we arrive at our goal.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">UserEntity</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span></code></pre></figure>

  </div>
</div>

<p>A query can also have a predicate, sort, and a limit. A problem with Core Data is that it lacks a declarative API, let’s fix that.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">predicate</span><span class="p">:</span> <span class="kt">NSPredicate</span><span class="p">?</span>
    <span class="k">let</span> <span class="nv">sortDescriptors</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSSortDescriptor</span><span class="p">]</span>
    <span class="k">let</span> <span class="nv">fetchLimit</span><span class="p">:</span> <span class="kt">Int</span><span class="p">?</span>
<span class="p">}</span></code></pre></figure>

<p><span id="more2" title="hint2" anchorid="2">Integrating this query</span> on our fetch method doesn’t change its signature, but enables query modification with a fluent API.</p>

<div id="hint2" class="aside">
    <br />
  <div class="aside-text">

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">EntityQuery</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">fetchRequest</span><span class="p">:</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
    
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">all</span><span class="p">:</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="n">fetch</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">ManagedObject</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">query</span><span class="p">:</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="o">.</span><span class="n">all</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">T</span><span class="p">]</span> <span class="p">{</span>
    <span class="n">viewContext</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">query</span><span class="o">.</span><span class="n">fetchRequest</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">UserEntity</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span></code></pre></figure>

  </div>
</div>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">EntityQuery</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">limit</span><span class="p">(</span><span class="n">_</span> <span class="nv">count</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kt">EntityQuery</span><span class="p">(</span><span class="nv">predicate</span><span class="p">:</span> <span class="n">predicate</span><span class="p">,</span>
                    <span class="nv">sortDescriptors</span><span class="p">:</span> <span class="n">sortDescriptors</span><span class="p">,</span>
                    <span class="nv">fetchLimit</span><span class="p">:</span> <span class="n">count</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">UserEntity</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span><span class="o">.</span><span class="nf">limit</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span></code></pre></figure>

<p><span id="more3" title="hint3" anchorid="3">Convenience queries</span> are also possible with minimal additions to the API surface.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="o">.</span><span class="n">minors</span><span class="p">)</span>      <span class="c1">// fetch by age</span>
<span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="o">.</span><span class="nf">id</span><span class="p">(</span><span class="n">user</span><span class="o">.</span><span class="n">id</span><span class="p">))</span> <span class="c1">// fetch by ID</span></code></pre></figure>

<div id="hint3" class="aside">
  <div class="aside-text">
      <p>Implementation</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">EntityQuery</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">minors</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">UserEntity</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kt">EntityQuery</span><span class="p">(</span><span class="nv">predicate</span><span class="p">:</span> <span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"age &lt; 18"</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">EntityQuery</span> <span class="k">where</span> <span class="kt">T</span><span class="p">:</span> <span class="kt">NSManagedObject</span> <span class="o">&amp;</span> <span class="kt">Identifiable</span><span class="p">,</span> <span class="kt">T</span><span class="o">.</span><span class="kt">ID</span> <span class="o">==</span> <span class="kt">UUID</span><span class="p">?</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">id</span><span class="p">(</span><span class="n">_</span> <span class="nv">id</span><span class="p">:</span> <span class="kt">UUID</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kt">EntityQuery</span><span class="p">(</span><span class="nv">predicate</span><span class="p">:</span> <span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"id == %@"</span><span class="p">,</span> <span class="n">id</span> <span class="k">as</span> <span class="kt">NSUUID</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

  </div>
</div>

<h2 id="data-first">Data First</h2>

<p>Let’s reflect on the benefits of a declarative approach:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Procedural</span>
<span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">User</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="s">"User"</span><span class="p">)</span>
<span class="n">request</span><span class="o">.</span><span class="n">predicate</span> <span class="o">=</span> <span class="kt">NSCompoundPredicate</span><span class="p">(</span><span class="nv">type</span><span class="p">:</span> <span class="o">.</span><span class="n">and</span><span class="p">,</span> <span class="nv">subpredicates</span><span class="p">:</span> <span class="p">[</span>
    <span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"age &gt; %d"</span><span class="p">,</span> <span class="mi">18</span><span class="p">),</span>
    <span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"isActive == YES"</span><span class="p">)</span>
<span class="p">])</span>
<span class="n">request</span><span class="o">.</span><span class="n">sortDescriptors</span> <span class="o">=</span> <span class="p">[</span>
    <span class="kt">NSSortDescriptor</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="s">"lastName"</span><span class="p">,</span> <span class="nv">ascending</span><span class="p">:</span> <span class="kc">true</span><span class="p">),</span>
    <span class="kt">NSSortDescriptor</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="s">"firstName"</span><span class="p">,</span> <span class="nv">ascending</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">]</span>
<span class="n">request</span><span class="o">.</span><span class="n">fetchLimit</span> <span class="o">=</span> <span class="mi">20</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Declarative</span>
<span class="k">let</span> <span class="nv">query</span> <span class="o">=</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">UserEntity</span><span class="o">&gt;.</span><span class="n">all</span>
    <span class="o">.</span><span class="nf">ascending</span><span class="p">(</span><span class="s">"lastName"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">ascending</span><span class="p">(</span><span class="s">"firstName"</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">and</span><span class="p">(</span><span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"age &gt; %d"</span><span class="p">,</span> <span class="mi">18</span><span class="p">))</span>
    <span class="o">.</span><span class="nf">and</span><span class="p">(</span><span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"isActive == YES"</span><span class="p">))</span>
    <span class="o">.</span><span class="nf">limit</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span></code></pre></figure>

<p>Gone are the underlying mechanics of <code>NSFetchRequest</code>. Clients are now closer to what they want to achieve, not how. Additional benefits:</p>
<ul>
  <li>Queries become composable and reusable</li>
  <li>The structure prevents invalid states</li>
  <li>Declarative queries are easier to inspect and test, compared to procedural code</li>
  <li>Separation of concerns between how and what to fetch</li>
  <li>Fetch mechanics are centralize in a single point that interprets data, decreasing the code and its possible mistakes</li>
</ul>

<p>Through the brief history of computing, many have <span id="more4" title="hint4" anchorid="4">noted</span> the convenience and power of shifting procedural complexity to declarative data.</p>

<div id="hint4" class="aside">
    <br />
  <div class="aside-text">
<blockquote>Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious. –The Mythical Man-Month (1975)</blockquote>

<blockquote>Rule of Representation: Fold knowledge into data, so program logic can be stupid and robust. Even the simplest procedural logic is hard for humans to verify, but quite complex data structures are fairly easy to model and reason about. –<a href="http://www.catb.org/esr/writings/taoup/html/ch01s06.html#id2878263">Basics of the Unix Philosophy</a></blockquote>

<blockquote>Bad programmers worry about the code. Good programmers worry about data structures and their relationships. –Linus Torvalds</blockquote>

<blockquote>Smart data structures and dumb code works a lot better than the other way around. –Eric S. Raymond, The Cathedral and The Bazaar.</blockquote>

Or a similar argument before computers existed:
<blockquote>Have the argument clear in your mind, the words will follow naturally. –<a href="https://en.wikipedia.org/wiki/Cato_the_Elder">Cato the Elder</a></blockquote>
  </div>
</div>

<h2 id="fetching-domain">Fetching Domain</h2>

<p>This would be nice to have:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">UserEntity</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">users</span><span class="p">:</span> <span class="p">[</span><span class="kt">User</span><span class="p">]</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="nf">fetch</span><span class="p">()</span></code></pre></figure>

<p>To implement this we know</p>
<ul>
  <li>managed objects need to map to domain,</li>
  <li>both the fetch and the mapping needs to be tied to the generic type,
which leads to the following solution:</li>
</ul>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">DomainConvertible</span> <span class="p">{</span>
    <span class="kd">associatedtype</span> <span class="kt">DomainModel</span>
    <span class="kd">func</span> <span class="nf">toDomain</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">DomainModel</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">UserEntity</span><span class="p">:</span> <span class="kt">DomainConvertible</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">func</span> <span class="nf">toDomain</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">User</span> <span class="p">{</span>
        <span class="kt">User</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="n">id</span><span class="p">,</span> <span class="nv">name</span><span class="p">:</span> <span class="n">name</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="n">fetch</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">ManagedObject</span> <span class="o">&amp;</span> <span class="kt">DomainConvertible</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">query</span><span class="p">:</span> <span class="kt">EntityQuery</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">T</span><span class="o">.</span><span class="kt">DomainModel</span><span class="p">]</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">objects</span><span class="p">:</span> <span class="p">[</span><span class="kt">T</span><span class="p">]</span> <span class="o">=</span> <span class="k">try</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
    <span class="k">return</span> <span class="k">try</span> <span class="n">objects</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="k">try</span> <span class="nv">$0</span><span class="o">.</span><span class="nf">toDomain</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h2 id="persisting-domain">Persisting Domain</h2>

<p>A similar protocol is used to map back from model to database:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">EntityConvertible</span> <span class="p">{</span>
    <span class="kd">associatedtype</span> <span class="kt">MO</span><span class="p">:</span> <span class="kt">NSManagedObject</span>
    <span class="kd">func</span> <span class="nf">toEntity</span><span class="p">(</span><span class="nv">container</span><span class="p">:</span> <span class="kt">EntityContainer</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">MO</span><span class="p">?</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">User</span><span class="p">:</span> <span class="kt">EntityConvertible</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span></code></pre></figure>

<p>For updates create a <code>User.Update</code> that has the same properties, but all except the ID are optional. When we submit an update, it looks for the object by ID and updates any non-nil property. For objects with relations this requires <a href="https://github.com/janodev/EntityStack/blob/main/Sources/Tests/Models/Person.swift#L60">some effort</a>, since we have to upsert the objects.</p>

<p>An interesting case is bidirectional relationships, for instance,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dog.owner &lt;--n 1--&gt; Person.dogs
</code></pre></div></div>

<p>There are two solutions to avoid an infinite loop:</p>
<ul>
  <li>change <code>dog.owner</code> from person to UUID</li>
  <li>or remove the <code>dog.owner</code> property</li>
</ul>

<p>Both are valid. Prefer removing the <code>dog.owner</code> if you primarily access dogs through their owners.</p>

<h2 id="benefits">Benefits</h2>

<p>This architecture provides several advantages:</p>
<ul>
  <li>Type-safe queries prevent runtime errors</li>
  <li>Mapping between domain and persistence</li>
  <li>Testable code through protocol abstractions</li>
  <li>Bidirectional conversion between domain models and entities</li>
  <li>Easier <span id="more5" title="hint5" anchorid="5">thread-safety</span> thanks to value objects</li>
</ul>

<div id="hint5" class="aside">
  <h4>Thread-Safety in Core Data</h4>
  <div class="aside-text">
<p>These are the rules:</p>
<ul>
<li>For light tasks use main thread.</li>
<li>For heavy tasks use <a href="https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640564-performbackgroundtask">performBackgroundTask</a>.</li>
<li>For complex operations child contexts with type <a href="https://developer.apple.com/documentation/coredata/nsmanagedobjectcontextconcurrencytype/nsprivatequeueconcurrencytype">NSPrivateQueueConcurrencyType</a> that merges changes back to the main context. This also lets you discard the child context to rollback changes.</li>
<li>To send objects across threads use domain structs or <a href="https://developer.apple.com/documentation/coredata/nsmanagedobjectid">NSManagedObjectID</a>, never managed objects.</li>
</ul>
  </div>
</div>

<p>The cost is minimal - just a thin layer over Core Data that makes it significantly more robust and maintainable. The <a href="https://github.com/janodevorg/CoreDataStack">source code</a> for the article is in GitHub.</p>]]></content><author><name></name></author><category term="apple" /><category term="coredata" /><category term="apple" /><category term="coredata" /><summary type="html"><![CDATA[Let's build a type-safe Core Data stack that's both powerful and pleasant to use.]]></summary></entry><entry><title type="html">Modeling Screen States</title><link href="https://jano.dev/apple/concurrency/swiftui/2024/12/21/Modeling-Screen-States-AsyncSequence.html" rel="alternate" type="text/html" title="Modeling Screen States" /><published>2024-12-21T03:03:03+01:00</published><updated>2024-12-21T03:03:03+01:00</updated><id>https://jano.dev/apple/concurrency/swiftui/2024/12/21/Modeling-Screen-States-AsyncSequence</id><content type="html" xml:base="https://jano.dev/apple/concurrency/swiftui/2024/12/21/Modeling-Screen-States-AsyncSequence.html"><![CDATA[<p>Any data-fetching screen typically transitions through states such as idle, loading, loaded, and failure. In this post, I’ll demonstrate the evolution of a pattern, starting with a Combine-based implementation and refactoring it to a simpler solution using <code class="language-plaintext highlighter-rouge">async/await</code> and Swift’s native Observation-backed <code class="language-plaintext highlighter-rouge">AsyncSequence</code>.</p>

<h2>States</h2>

<p>I’ll represent states as an enum, along with a <code class="language-plaintext highlighter-rouge">LoadingProgress</code> object for more granular updates.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">public</span> <span class="kd">enum</span> <span class="kt">LoadingState</span><span class="o">&lt;</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Hashable</span> <span class="o">&amp;</span> <span class="kt">Sendable</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">Hashable</span><span class="p">,</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">idle</span>
    <span class="k">case</span> <span class="nf">loading</span><span class="p">(</span><span class="kt">LoadingProgress</span><span class="p">?)</span>
    <span class="k">case</span> <span class="nf">failure</span><span class="p">(</span><span class="kt">HashableError</span><span class="p">)</span>
    <span class="k">case</span> <span class="nf">loaded</span><span class="p">(</span><span class="kt">Value</span><span class="p">)</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">failure</span><span class="p">(</span><span class="n">_</span> <span class="nv">error</span><span class="p">:</span> <span class="kd">any</span> <span class="kt">Error</span> <span class="o">&amp;</span> <span class="kt">Sendable</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">LoadingState</span> <span class="p">{</span>
        <span class="o">.</span><span class="nf">failure</span><span class="p">(</span><span class="kt">HashableError</span><span class="p">(</span><span class="n">error</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">public</span> <span class="kd">struct</span> <span class="kt">LoadingProgress</span><span class="p">:</span> <span class="kt">Hashable</span><span class="p">,</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="k">let</span> <span class="nv">isCanceled</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">?</span>
    <span class="kd">public</span> <span class="k">let</span> <span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
    <span class="kd">public</span> <span class="k">let</span> <span class="nv">percent</span><span class="p">:</span> <span class="kt">Int</span><span class="p">?</span> <span class="c1">// 0 to 100</span>
<span class="p">}</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">HashableError</code> wraps any <code class="language-plaintext highlighter-rouge">Error &amp; Sendable</code> to preserve Hashable/Equatable semantics while still exposing the underlying error via its <code class="language-plaintext highlighter-rouge">error</code> property.</p>

<h2>Combine-Based</h2>

<p>To standardize the handling of loadable content, we can define a protocol. A Combine-based version of this protocol might look like this:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">/// An object that loads content (Legacy Combine version).</span>
<span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">Loadable_Combine</span> <span class="p">{</span>
    <span class="kd">associatedtype</span> <span class="kt">Value</span><span class="p">:</span> <span class="kt">Sendable</span>
    
    <span class="c1">/// Emits states about the loading process.</span>
    <span class="k">var</span> <span class="nv">state</span><span class="p">:</span> <span class="kt">PassthroughSubject</span><span class="o">&lt;</span><span class="kt">LoadingState</span><span class="o">&lt;</span><span class="kt">Value</span><span class="o">&gt;</span><span class="p">,</span> <span class="kt">Never</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
        
    <span class="c1">/// Initiates the load process.</span>
    <span class="kd">func</span> <span class="nf">load</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>This approach worked, but it relied on the <code class="language-plaintext highlighter-rouge">PassthroughSubject</code>, <code class="language-plaintext highlighter-rouge">sink</code>, and <code class="language-plaintext highlighter-rouge">AnyCancellable</code> boilerplate common to Combine. While powerful, modern Swift concurrency offers a cleaner path.</p>

<h2>AsyncSequence</h2>

<p>Apple’s focus has clearly shifted to <code class="language-plaintext highlighter-rouge">async/await</code> and <code class="language-plaintext highlighter-rouge">AsyncSequence</code>. They allow us to consume streams of values with a simple <code class="language-plaintext highlighter-rouge">for await</code> loop, integrating naturally with Structured Concurrency and removing the need for manual subscription management. If needed, <a href="https://github.com/apple/swift-async-algorithms">async algorithms</a> provides operators similar to Combine.</p>

<p>Our modern <a href="https://github.com/janodevorg/LoadingView/blob/main/Sources/Main/Loadable.swift">Loadable</a> protocol looks like this:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@MainActor</span>
<span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">Loadable</span> <span class="p">{</span>
    <span class="kd">associatedtype</span> <span class="kt">Value</span><span class="p">:</span> <span class="kt">Hashable</span><span class="p">,</span> <span class="kt">Sendable</span>

    <span class="c1">/// An asynchronous sequence that publishes the loading state.</span>
    <span class="k">var</span> <span class="nv">state</span><span class="p">:</span> <span class="kd">any</span> <span class="kt">AsyncSequence</span><span class="o">&lt;</span><span class="kt">LoadingState</span><span class="o">&lt;</span><span class="kt">Value</span><span class="o">&gt;</span><span class="p">,</span> <span class="kt">Never</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>

    <span class="c1">/// The latest state for quick sync when views reappear.</span>
    <span class="k">var</span> <span class="nv">currentState</span><span class="p">:</span> <span class="kt">LoadingState</span><span class="o">&lt;</span><span class="kt">Value</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>

    <span class="c1">/// Flag indicating if the loading operation has been cancelled.</span>
    <span class="k">var</span> <span class="nv">isCanceled</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>

    <span class="c1">/// Cancels the ongoing loading operation.</span>
    <span class="kd">func</span> <span class="nf">cancel</span><span class="p">()</span>

    <span class="c1">/// Resets the loadable to its initial state.</span>
    <span class="kd">func</span> <span class="nf">reset</span><span class="p">()</span>

    <span class="c1">/// Initiates the loading of the value.</span>
    <span class="kd">func</span> <span class="nf">load</span><span class="p">()</span> <span class="k">async</span>
<span class="p">}</span></code></pre></figure>

<h3 id="base-implementation">Base implementation</h3>

<p>Implementing this protocol is more verbose than using Combine, but that can be solved with a <a href="https://github.com/janodevorg/LoadingView/blob/main/Sources/Main/Extra/BaseLoadable.swift">base implementation</a>. Simply subclass and override the <code class="language-plaintext highlighter-rouge">fetch()</code> method. <code class="language-plaintext highlighter-rouge">BaseLoadable</code> drives the Observation-backed <code class="language-plaintext highlighter-rouge">AsyncSequence</code> and yields <code class="language-plaintext highlighter-rouge">.loading</code>, <code class="language-plaintext highlighter-rouge">.loaded</code>, and <code class="language-plaintext highlighter-rouge">.failure</code> states for you.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@MainActor</span>
<span class="kd">class</span> <span class="kt">UserLoader</span><span class="p">:</span> <span class="kt">BaseLoadable</span><span class="o">&lt;</span><span class="kt">User</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="kd">func</span> <span class="nf">fetch</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">User</span> <span class="p">{</span>
        <span class="c1">// Your async loading logic here</span>
        <span class="k">try</span> <span class="k">await</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">sleep</span><span class="p">(</span><span class="nv">nanoseconds</span><span class="p">:</span> <span class="mi">1_000_000_000</span><span class="p">)</span>
        
        <span class="c1">// Just return the value or throw an error</span>
        <span class="k">return</span> <span class="kt">User</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"Jane Doe"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="the-loadingview">The LoadingView</h3>

<p>With this new model, the <code class="language-plaintext highlighter-rouge">LoadingView</code> also becomes simpler. It no longer needs a <code class="language-plaintext highlighter-rouge">ViewModel</code>; it can directly observe the <code class="language-plaintext highlighter-rouge">Loadable</code> object’s state using the <code class="language-plaintext highlighter-rouge">.task</code> modifier.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">LoadingView</span><span class="o">&lt;</span><span class="kt">L</span><span class="p">:</span> <span class="kt">Loadable</span><span class="p">,</span> <span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">loader</span><span class="p">:</span> <span class="kt">L</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">content</span><span class="p">:</span> <span class="p">(</span><span class="kt">L</span><span class="o">.</span><span class="kt">Value</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</span>
    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">loadingState</span><span class="p">:</span> <span class="kt">LoadingState</span><span class="o">&lt;</span><span class="kt">L</span><span class="o">.</span><span class="kt">Value</span><span class="o">&gt;</span> <span class="o">=</span> <span class="o">.</span><span class="n">idle</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">loader</span><span class="p">:</span> <span class="kt">L</span><span class="p">,</span> <span class="kd">@ViewBuilder</span> <span class="nv">content</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">L</span><span class="o">.</span><span class="kt">Value</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">loader</span> <span class="o">=</span> <span class="n">loader</span>
        <span class="k">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">content</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Group</span> <span class="p">{</span>
            <span class="k">switch</span> <span class="n">loadingState</span> <span class="p">{</span>
            <span class="k">case</span> <span class="o">.</span><span class="nv">idle</span><span class="p">:</span>
                <span class="kt">ProgressView</span><span class="p">(</span><span class="s">"Loading..."</span><span class="p">)</span>
            <span class="k">case</span> <span class="o">.</span><span class="nf">loading</span><span class="p">(</span><span class="k">let</span> <span class="nv">progress</span><span class="p">):</span>
                <span class="kt">ProgressView</span><span class="p">(</span><span class="n">progress</span><span class="p">?</span><span class="o">.</span><span class="n">message</span> <span class="p">??</span> <span class="s">"Loading…"</span><span class="p">)</span>
            <span class="k">case</span> <span class="o">.</span><span class="nf">loaded</span><span class="p">(</span><span class="k">let</span> <span class="nv">value</span><span class="p">):</span>
                <span class="nf">content</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
            <span class="k">case</span> <span class="o">.</span><span class="nf">failure</span><span class="p">(</span><span class="k">let</span> <span class="nv">error</span><span class="p">):</span>
                <span class="kt">Text</span><span class="p">(</span><span class="n">error</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">localizedDescription</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">loadingState</span> <span class="o">!=</span> <span class="n">loader</span><span class="o">.</span><span class="n">currentState</span> <span class="p">{</span>
                <span class="n">loadingState</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="n">currentState</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="n">task</span> <span class="p">{</span>
            <span class="k">for</span> <span class="k">await</span> <span class="n">state</span> <span class="k">in</span> <span class="n">loader</span><span class="o">.</span><span class="n">state</span> <span class="p">{</span>
                <span class="k">self</span><span class="o">.</span><span class="n">loadingState</span> <span class="o">=</span> <span class="n">state</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="state-persistence-with-observation">State Persistence with Observation</h3>

<p>A key problem with <code class="language-plaintext highlighter-rouge">AsyncStream</code> is that it’s a “one-time” event pipe. If you navigate away from a view, its <code class="language-plaintext highlighter-rouge">.task</code> is cancelled. When you navigate back, a new observer is created, but the <code class="language-plaintext highlighter-rouge">AsyncStream</code> doesn’t “replay” its last value. This causes the UI to revert to its <code class="language-plaintext highlighter-rouge">.idle</code> state, even if data was already loaded.</p>

<p>The latest library uses Swift’s Observation framework to back the <code class="language-plaintext highlighter-rouge">state</code> <code class="language-plaintext highlighter-rouge">AsyncSequence</code>, which means:</p>

<ul>
  <li>The latest value is replayed to new observers automatically.</li>
  <li>Multiple observers can consume the same stream safely.</li>
  <li><code class="language-plaintext highlighter-rouge">currentState</code> gives you an immediate snapshot for view re-sync on appear.</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">BaseLoadable</code>, <code class="language-plaintext highlighter-rouge">RetryableLoader</code>, and <code class="language-plaintext highlighter-rouge">ConcurrencyLimitingLoadable</code> all use Observation-backed streams. For types that still use <code class="language-plaintext highlighter-rouge">AsyncStream</code> internally (for example, <code class="language-plaintext highlighter-rouge">DebouncingLoadable</code>), wrap them in another loader if you need multi-observer replay semantics.</p>

<h2>Composing loaders</h2>

<p>The <code class="language-plaintext highlighter-rouge">Loadable</code> protocol and <code class="language-plaintext highlighter-rouge">BaseLoadable</code> class create a powerful foundation for composition. The library includes wrappers that add functionality to any <code class="language-plaintext highlighter-rouge">Loadable</code> object.</p>

<h4>Retry</h4>

<p>Loaders can be composed. Wrapping a loader <code class="language-plaintext highlighter-rouge">RetryableLoader</code> provides automatic retry capabilities with exponential backoff.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Create a loader that might fail</span>
<span class="k">let</span> <span class="nv">flakeyLoader</span> <span class="o">=</span> <span class="kt">FlakeyLoader</span><span class="p">(</span><span class="nv">successAfterAttempts</span><span class="p">:</span> <span class="mi">3</span><span class="p">)</span>

<span class="c1">// Wrap it to add retry logic</span>
<span class="k">let</span> <span class="nv">retryableLoader</span> <span class="o">=</span> <span class="kt">RetryableLoader</span><span class="p">(</span>
    <span class="nv">base</span><span class="p">:</span> <span class="n">flakeyLoader</span><span class="p">,</span>
    <span class="nv">maxAttempts</span><span class="p">:</span> <span class="mi">5</span>
<span class="p">)</span>

<span class="c1">// Use it in the view. It will automatically retry on failure.</span>
<span class="kt">LoadingView</span><span class="p">(</span><span class="nv">loader</span><span class="p">:</span> <span class="n">retryableLoader</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span></code></pre></figure>

<h4>Debounce</h4>

<p>Another example for composition is the debouncing of values in a search field. This wrapper delays load calls until the user stops typing.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Create a loader that performs a search</span>
<span class="k">let</span> <span class="nv">searchLoader</span> <span class="o">=</span> <span class="kt">SearchLoader</span><span class="p">()</span>

<span class="c1">// Wrap it to add debouncing</span>
<span class="k">let</span> <span class="nv">debouncedLoader</span> <span class="o">=</span> <span class="k">await</span> <span class="kt">DebouncingLoadable</span><span class="p">(</span>
    <span class="nv">wrapping</span><span class="p">:</span> <span class="n">searchLoader</span><span class="p">,</span>
    <span class="nv">debounceInterval</span><span class="p">:</span> <span class="mf">0.5</span> <span class="c1">// 500ms</span>
<span class="p">)</span>

<span class="c1">// In the view, call load() on every keystroke.</span>
<span class="c1">// The wrapper ensures the actual search is only triggered when needed.</span>
<span class="kt">TextField</span><span class="p">(</span><span class="s">"Search..."</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="n">$searchText</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">onChange</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">searchText</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span> <span class="k">await</span> <span class="n">debouncedLoader</span><span class="o">.</span><span class="nf">load</span><span class="p">()</span> <span class="p">}</span>
    <span class="p">}</span></code></pre></figure>

<h4>Limiting concurrency</h4>

<p>This example uses a token bucket, see the source at <a href="https://github.com/janodevorg/LoadingView/blob/main/Sources/Main/Extra/ConcurrencyLimitingLoadable.swift">ConcurrencyLimitingLoadable</a>.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">Button</span><span class="p">(</span><span class="s">"Start Downloads"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">baseLoader</span> <span class="o">=</span> <span class="kt">ParallelDownloadLoader</span><span class="p">(</span><span class="nv">itemCount</span><span class="p">:</span> <span class="n">numberOfItems</span><span class="p">)</span>
    <span class="n">loader</span> <span class="o">=</span> <span class="kt">ConcurrencyLimitingLoadable</span><span class="p">(</span>
        <span class="nv">wrapping</span><span class="p">:</span> <span class="n">baseLoader</span><span class="p">,</span>
        <span class="nv">concurrencyLimit</span><span class="p">:</span> <span class="n">concurrencyLimit</span>
    <span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<h2>Conclusion</h2>

<p>This is what we got</p>

<ul>
  <li>Consistency with a reusable solution</li>
  <li>Type-safety with an enum-based state</li>
  <li>Customizable views</li>
  <li>Progress tracking</li>
  <li>Composable loaders</li>
</ul>

<p>The <a href="https://github.com/janodevorg/LoadingView">source</a> is available on GitHub. The final architecture provides a reusable, powerful, and easy-to-use pattern for managing screen states in any SwiftUI application.</p>]]></content><author><name></name></author><category term="apple" /><category term="concurrency" /><category term="swiftui" /><category term="apple" /><category term="concurrency" /><category term="swiftui" /><category term="combine" /><category term="state-management" /><summary type="html"><![CDATA[Learn how to model screen states in SwiftUI, evolving from a Combine-based pattern to a modern, robust solution with AsyncSequence and Swift's Observation framework.]]></summary></entry></feed>