<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.eliaszsawicki.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.eliaszsawicki.com/" rel="alternate" type="text/html" /><updated>2026-07-14T04:49:54+00:00</updated><id>https://www.eliaszsawicki.com/feed.xml</id><title type="html">Eliasz Sawicki</title><subtitle>Build a Jekyll blog in minutes, without touching the command line.</subtitle><entry><title type="html">Swift Testing: Test.cancel()</title><link href="https://www.eliaszsawicki.com/swift-testing-test-cancel/" rel="alternate" type="text/html" title="Swift Testing: Test.cancel()" /><published>2026-07-13T00:00:00+00:00</published><updated>2026-07-13T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/swift-testing-test-cancel</id><content type="html" xml:base="https://www.eliaszsawicki.com/swift-testing-test-cancel/"><![CDATA[<p>Swift Testing 2.0 (Swift 6.4) adds <code class="language-plaintext highlighter-rouge">Test.cancel()</code> — a way to programmatically cancel the currently running test, marking it as cancelled rather than failed.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Test</span> <span class="kd">func</span> <span class="nf">featureRequiresDevice</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">await</span> <span class="kt">DeviceCapability</span><span class="o">.</span><span class="n">isSupported</span> <span class="k">else</span> <span class="p">{</span>
        <span class="kt">Test</span><span class="o">.</span><span class="n">current</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="c1">// test body runs only when supported</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is different from <code class="language-plaintext highlighter-rouge">#require</code> (which records a failure) or throwing a <code class="language-plaintext highlighter-rouge">Skip</code> (which marks the test skipped). <code class="language-plaintext highlighter-rouge">Test.cancel()</code> cancels the underlying task — the test shows up as cancelled in the report, signalling “not applicable” rather than “broken.”</p>

<p>Because it goes through Swift concurrency cancellation, any <code class="language-plaintext highlighter-rouge">try await</code> after the call will throw <code class="language-plaintext highlighter-rouge">CancellationError</code> — so a bare <code class="language-plaintext highlighter-rouge">cancel()</code> followed by <code class="language-plaintext highlighter-rouge">return</code> is the safest pattern.</p>

<p>Cancellation also propagates to child tests in a suite:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Test</span> <span class="kd">func</span> <span class="nf">suite</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kt">ProcessInfo</span><span class="o">.</span><span class="n">processInfo</span><span class="o">.</span><span class="n">environment</span><span class="p">[</span><span class="s">"CI"</span><span class="p">]</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
        <span class="kt">Test</span><span class="o">.</span><span class="n">current</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="c1">// child tests run only in CI</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Takeaway:</strong> use <code class="language-plaintext highlighter-rouge">Test.cancel()</code> when a test is conditionally irrelevant — not wrong, just not applicable. Reserve <code class="language-plaintext highlighter-rouge">#require</code> for preconditions that <em>should</em> hold, and keep <code class="language-plaintext highlighter-rouge">cancel()</code> for environment or capability gates.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="testing" /><category term="swift6" /><summary type="html"><![CDATA[Swift Testing 2.0 (Swift 6.4) adds Test.cancel() — a way to programmatically cancel the currently running test, marking it as cancelled rather than failed.]]></summary></entry><entry><title type="html">Swift 6.4: Task Cancellation Shields</title><link href="https://www.eliaszsawicki.com/task-cancellation-shield/" rel="alternate" type="text/html" title="Swift 6.4: Task Cancellation Shields" /><published>2026-07-06T00:00:00+00:00</published><updated>2026-07-06T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/task-cancellation-shield</id><content type="html" xml:base="https://www.eliaszsawicki.com/task-cancellation-shield/"><![CDATA[<p>SE-0504 adds <code class="language-plaintext highlighter-rouge">withTaskCancellationShield</code> — a way to run a closure where <code class="language-plaintext highlighter-rouge">Task.isCancelled</code> always returns <code class="language-plaintext highlighter-rouge">false</code>, regardless of the outer task’s cancellation state.</p>

<p>The problem it solves: cleanup and rollback work that <em>must</em> finish even when a task has been cancelled. Without a shield, awaits inside cancelled tasks can skip or short-circuit unexpectedly.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">closeConnection</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">await</span> <span class="n">withTaskCancellationShield</span> <span class="p">{</span>
        <span class="k">await</span> <span class="n">database</span><span class="o">.</span><span class="nf">close</span><span class="p">()</span>   <span class="c1">// runs to completion even if outer task is cancelled</span>
    <span class="p">}</span>
    <span class="c1">// Task.isCancelled reflects the real state again here</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A good one-two punch is pairing it with <code class="language-plaintext highlighter-rouge">defer</code>:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">performWork</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">defer</span> <span class="p">{</span>
        <span class="n">withTaskCancellationShield</span> <span class="p">{</span>
            <span class="nf">cleanup</span><span class="p">()</span>   <span class="c1">// deferred cleanup that must not be skipped</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">try</span> <span class="k">await</span> <span class="nf">doTheWork</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The shield also prevents cancellation from propagating into child tasks — <code class="language-plaintext highlighter-rouge">async let</code> bindings and task groups inside the closure won’t be automatically cancelled.</p>

<p><strong>Constraints worth knowing:</strong></p>
<ul>
  <li>Keep shielded regions short — finish or roll back work already started, not hide cancellation from long-running operations</li>
  <li>Once the closure returns, the outer task’s cancellation state is restored</li>
</ul>

<p><strong>Takeaway:</strong> reach for <code class="language-plaintext highlighter-rouge">withTaskCancellationShield</code> when you have cleanup that cannot be skipped on cancellation — it’s the missing piece for reliable resource teardown in structured concurrency.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="concurrency" /><category term="swift6" /><summary type="html"><![CDATA[SE-0504 adds withTaskCancellationShield — a way to run a closure where Task.isCancelled always returns false, regardless of the outer task’s cancellation state.]]></summary></entry><entry><title type="html">Trust Insights: Detecting Coerced Actions in iOS 27</title><link href="https://www.eliaszsawicki.com/trust-insights/" rel="alternate" type="text/html" title="Trust Insights: Detecting Coerced Actions in iOS 27" /><published>2026-06-29T00:00:00+00:00</published><updated>2026-06-29T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/trust-insights</id><content type="html" xml:base="https://www.eliaszsawicki.com/trust-insights/"><![CDATA[<p>Summary of WWDC2026 session about:
Trust Insights (iOS 27) — behavioral coercion detection framework.</p>

<p><strong>The problem it solves:</strong> Social engineering attacks where the user performs the action themselves (authenticated, legitimate), so MFA/biometrics are useless. Need a signal for coerced vs. genuine intent.</p>

<p><strong>Integration flow:</strong></p>
<ul>
  <li>Requires an entitlement/capability in Xcode</li>
  <li>Client-side Swift API only (no server integration needed)</li>
  <li>Create <code class="language-plaintext highlighter-rouge">InsightContext</code> with <code class="language-plaintext highlighter-rouge">operationCategory</code> (payment, account, resourceUse, communication, other) + <code class="language-plaintext highlighter-rouge">InsightEvaluation</code></li>
  <li>Call <code class="language-plaintext highlighter-rouge">requestEvaluation()</code> async — takes a few seconds, needs internet</li>
  <li>Check authorization status first (user can disable it in Settings)</li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">guard</span> <span class="k">await</span> <span class="kt">InsightContext</span><span class="o">.</span><span class="n">authorizationStatus</span> <span class="o">==</span> <span class="o">.</span><span class="n">authorized</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>

<span class="k">let</span> <span class="nv">context</span> <span class="o">=</span> <span class="kt">InsightContext</span><span class="p">(</span>
    <span class="nv">operationCategory</span><span class="p">:</span> <span class="o">.</span><span class="n">payment</span><span class="p">,</span>
    <span class="nv">evaluation</span><span class="p">:</span> <span class="kt">InsightEvaluation</span><span class="p">()</span>
<span class="p">)</span>
<span class="k">let</span> <span class="nv">insight</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">context</span><span class="o">.</span><span class="nf">requestEvaluation</span><span class="p">()</span>

<span class="k">switch</span> <span class="n">insight</span><span class="o">.</span><span class="n">isLikelyBeingCoached</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">high</span><span class="p">:</span>    <span class="nf">showCoercionWarning</span><span class="p">()</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">medium</span><span class="p">:</span>  <span class="nf">requireAdditionalVerification</span><span class="p">()</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">unknown</span><span class="p">:</span> <span class="k">break</span>  <span class="c1">// no evidence — not a green light</span>
<span class="p">}</span>

<span class="n">insight</span><span class="o">.</span><span class="nf">reportConsumption</span><span class="p">()</span>  <span class="c1">// mandatory</span>
</code></pre></div></div>

<p><strong>Result values for <code class="language-plaintext highlighter-rouge">IsLikelyBeingCoachedInsight</code>:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">unknown</code> — no evidence, but not to be treated as low risk</li>
  <li><code class="language-plaintext highlighter-rouge">medium</code> — introduce friction, additional verification, adjust risk scoring</li>
  <li><code class="language-plaintext highlighter-rouge">high</code> — warn the user before proceeding</li>
</ul>

<p><strong>Feedback (mandatory):</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">reportConsumption()</code> must be called per evaluation or you get rate-limited</li>
  <li>Offline fraud labels submitted via Apple Business Register (server-to-server) — optional but helps the model</li>
</ul>

<p><strong>Privacy architecture:</strong></p>
<ul>
  <li>Device-sourced signals (interaction patterns, timing, sensors) processed locally, inputs discarded immediately</li>
  <li>Only the single output value leaves the device</li>
  <li>Apple Account signals + velocity checks may be incorporated server-side</li>
  <li>Never touches Photos, Messages, Mail content</li>
  <li>User can disable in Settings (cooldown applies to prevent coaching into disabling it)</li>
</ul>

<p><strong>Best practices:</strong></p>
<ul>
  <li>Use it at high-value moments: P2P payments, irreversible actions, remote access grants, sensitive data sharing</li>
  <li>Never sole decision factor — integrate into existing risk logic</li>
  <li>Never treat unknown or missing value as safe</li>
  <li>Test with Xcode scheme launch argument overrides (sandbox in dev, production on App Store)</li>
</ul>

<p>Related: App Attest for verifying requests come from legitimate app instances.</p>]]></content><author><name></name></author><category term="devlog" /><category term="ios" /><category term="security" /><category term="swift" /><category term="fraud" /><summary type="html"><![CDATA[Summary of WWDC2026 session about: Trust Insights (iOS 27) — behavioral coercion detection framework.]]></summary></entry><entry><title type="html">Swift 6.2 Span: Safe, Non-Owning Buffer Views</title><link href="https://www.eliaszsawicki.com/swift-span/" rel="alternate" type="text/html" title="Swift 6.2 Span: Safe, Non-Owning Buffer Views" /><published>2026-06-22T00:00:00+00:00</published><updated>2026-06-22T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/swift-span</id><content type="html" xml:base="https://www.eliaszsawicki.com/swift-span/"><![CDATA[<p><code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> is Swift 6.2’s answer to <code class="language-plaintext highlighter-rouge">UnsafeBufferPointer</code>: a bounds-checked, non-owning view into a contiguous region of memory — with lifetime safety enforced by the compiler.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">numbers</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">span</span><span class="p">:</span> <span class="kt">Span</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">numbers</span><span class="o">.</span><span class="n">span</span>   <span class="c1">// zero-copy view, no allocation</span>

<span class="nf">print</span><span class="p">(</span><span class="n">span</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>      <span class="c1">// 1</span>
<span class="nf">print</span><span class="p">(</span><span class="n">span</span><span class="o">.</span><span class="n">count</span><span class="p">)</span>   <span class="c1">// 5</span>

<span class="k">for</span> <span class="n">value</span> <span class="k">in</span> <span class="n">span</span> <span class="p">{</span>
    <span class="nf">process</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The key property is that <code class="language-plaintext highlighter-rouge">Span</code> is <code class="language-plaintext highlighter-rouge">~Escapable</code> — it cannot outlive the storage it views. The compiler rejects any attempt to escape it:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">leakedSpan</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Span</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;</span> <span class="p">{</span>   <span class="c1">// ❌ Span&lt;Int&gt; is non-escapable</span>
    <span class="k">let</span> <span class="nv">numbers</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">numbers</span><span class="o">.</span><span class="n">span</span>             <span class="c1">// compile error: span would outlive numbers</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This makes the entire class of dangling-pointer bugs a compile-time error rather than a runtime crash. Compare with the old approach:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Before: no safety net</span>
<span class="kd">func</span> <span class="nf">riskyWork</span><span class="p">(</span><span class="nv">buffer</span><span class="p">:</span> <span class="kt">UnsafeBufferPointer</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="n">numbers</span><span class="o">.</span><span class="n">withUnsafeBufferPointer</span> <span class="p">{</span> <span class="nf">riskyWork</span><span class="p">(</span><span class="nv">buffer</span><span class="p">:</span> <span class="nv">$0</span><span class="p">)</span> <span class="p">}</span>  <span class="c1">// easy to misuse</span>

<span class="c1">// Swift 6.2: lifetime tracked by the compiler</span>
<span class="kd">func</span> <span class="nf">safeWork</span><span class="p">(</span><span class="nv">span</span><span class="p">:</span> <span class="kt">Span</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="nf">safeWork</span><span class="p">(</span><span class="nv">span</span><span class="p">:</span> <span class="n">numbers</span><span class="o">.</span><span class="n">span</span><span class="p">)</span>
</code></pre></div></div>

<p>For byte-level access there is <code class="language-plaintext highlighter-rouge">RawSpan</code>, and <code class="language-plaintext highlighter-rouge">MutableSpan&lt;T&gt;</code> covers write-enabled views (with the same lifetime guarantees).</p>

<p><strong>Takeaway:</strong> prefer <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> over <code class="language-plaintext highlighter-rouge">UnsafeBufferPointer</code> whenever you need a performant, zero-copy read over contiguous memory — the <code class="language-plaintext highlighter-rouge">~Escapable</code> constraint gives you the speed without the footguns.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="performance" /><category term="memory" /><category term="swift6" /><summary type="html"><![CDATA[Span&lt;T&gt; is Swift 6.2’s answer to UnsafeBufferPointer: a bounds-checked, non-owning view into a contiguous region of memory — with lifetime safety enforced by the compiler.]]></summary></entry><entry><title type="html">TCA: Injecting Different Reducers Into the Same View</title><link href="https://www.eliaszsawicki.com/tca-swappable-reducers/" rel="alternate" type="text/html" title="TCA: Injecting Different Reducers Into the Same View" /><published>2026-06-15T00:00:00+00:00</published><updated>2026-06-15T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/tca-swappable-reducers</id><content type="html" xml:base="https://www.eliaszsawicki.com/tca-swappable-reducers/"><![CDATA[<p>Sometimes the same UI needs different logic depending on context — a form that fetches data in production but returns stubs in previews, or a screen that behaves differently in two flows. In TCA you can handle this by making the view generic over its reducer.</p>

<p>The trick is constraining the generic parameter so the <code class="language-plaintext highlighter-rouge">State</code> and <code class="language-plaintext highlighter-rouge">Action</code> types match, while letting the concrete reducer vary at the call site.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">CounterView</span><span class="o">&lt;</span><span class="kt">R</span><span class="p">:</span> <span class="kt">Reducer</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">R</span><span class="o">.</span><span class="kt">State</span> <span class="o">==</span> <span class="kt">CounterFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">,</span>
                                            <span class="kt">R</span><span class="o">.</span><span class="kt">Action</span> <span class="o">==</span> <span class="kt">CounterFeature</span><span class="o">.</span><span class="kt">Action</span> <span class="p">{</span>
    <span class="kd">@Bindable</span> <span class="k">var</span> <span class="nv">store</span><span class="p">:</span> <span class="kt">Store</span><span class="o">&lt;</span><span class="kt">R</span><span class="o">.</span><span class="kt">State</span><span class="p">,</span> <span class="kt">R</span><span class="o">.</span><span class="kt">Action</span><span class="o">&gt;</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">VStack</span> <span class="p">{</span>
            <span class="kt">Text</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="n">store</span><span class="o">.</span><span class="n">count</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
            <span class="kt">Button</span><span class="p">(</span><span class="s">"+"</span><span class="p">)</span> <span class="p">{</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">increment</span><span class="p">)</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now define two reducers with the same <code class="language-plaintext highlighter-rouge">State</code>/<code class="language-plaintext highlighter-rouge">Action</code> but different logic:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Reducer</span>
<span class="kd">struct</span> <span class="kt">CounterFeature</span> <span class="p">{</span>
    <span class="kd">struct</span> <span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">count</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span>
    <span class="kd">enum</span> <span class="kt">Action</span> <span class="p">{</span> <span class="k">case</span> <span class="n">increment</span> <span class="p">}</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">Reducer</span><span class="o">&lt;</span><span class="kt">State</span><span class="p">,</span> <span class="kt">Action</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>
            <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
            <span class="k">case</span> <span class="o">.</span><span class="nv">increment</span><span class="p">:</span>
                <span class="n">state</span><span class="o">.</span><span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
                <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">@Reducer</span>
<span class="kd">struct</span> <span class="kt">PreviewCounterFeature</span> <span class="p">{</span>
    <span class="kd">typealias</span> <span class="kt">State</span> <span class="o">=</span> <span class="kt">CounterFeature</span><span class="o">.</span><span class="kt">State</span>
    <span class="kd">typealias</span> <span class="kt">Action</span> <span class="o">=</span> <span class="kt">CounterFeature</span><span class="o">.</span><span class="kt">Action</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">Reducer</span><span class="o">&lt;</span><span class="kt">State</span><span class="p">,</span> <span class="kt">Action</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>
            <span class="n">state</span><span class="o">.</span><span class="n">count</span> <span class="o">+=</span> <span class="mi">10</span>
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Inject whichever reducer the context needs:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Production</span>
<span class="kt">CounterView</span><span class="p">(</span><span class="nv">store</span><span class="p">:</span> <span class="kt">Store</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">())</span> <span class="p">{</span> <span class="kt">CounterFeature</span><span class="p">()</span> <span class="p">})</span>

<span class="c1">// Preview / test</span>
<span class="kt">CounterView</span><span class="p">(</span><span class="nv">store</span><span class="p">:</span> <span class="kt">Store</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">())</span> <span class="p">{</span> <span class="kt">PreviewCounterFeature</span><span class="p">()</span> <span class="p">})</span>
</code></pre></div></div>

<p>The view has no knowledge of which reducer it received. The generic constraint guarantees both reducers speak the same <code class="language-plaintext highlighter-rouge">State</code> and <code class="language-plaintext highlighter-rouge">Action</code>, so the view compiles against either without change.</p>

<p><strong>Takeaway:</strong> make a TCA view generic over <code class="language-plaintext highlighter-rouge">R: Reducer</code> with <code class="language-plaintext highlighter-rouge">where</code> constraints on <code class="language-plaintext highlighter-rouge">State</code> and <code class="language-plaintext highlighter-rouge">Action</code> to decouple the view from any specific reducer implementation.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="tca" /><category term="ios" /><category term="architecture" /><summary type="html"><![CDATA[Sometimes the same UI needs different logic depending on context — a form that fetches data in production but returns stubs in previews, or a screen that behaves differently in two flows. In TCA you can handle this by making the view generic over its reducer.]]></summary></entry><entry><title type="html">GCD Queue Combinations: What Serial/Concurrent and Sync/Async Actually Control</title><link href="https://www.eliaszsawicki.com/dispatch-queue-combinations/" rel="alternate" type="text/html" title="GCD Queue Combinations: What Serial/Concurrent and Sync/Async Actually Control" /><published>2026-06-01T00:00:00+00:00</published><updated>2026-06-01T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/dispatch-queue-combinations</id><content type="html" xml:base="https://www.eliaszsawicki.com/dispatch-queue-combinations/"><![CDATA[<p>Two independent axes, four combinations. They’re easy to conflate because the names sound similar, but they control completely different things.</p>

<p><strong>Serial vs. concurrent</strong> — how many tasks the <em>queue</em> runs at once.<br />
<strong>Sync vs. async</strong> — whether the <em>caller</em> blocks until the task finishes.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">serial</span>     <span class="o">=</span> <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="s">"serial"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">concurrent</span> <span class="o">=</span> <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="s">"concurrent"</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="o">.</span><span class="n">concurrent</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<p><strong>Serial + sync</strong> — caller blocks, queue runs one task at a time.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">serial</span><span class="o">.</span><span class="n">sync</span> <span class="p">{</span> <span class="nf">work</span><span class="p">()</span> <span class="p">}</span> <span class="c1">// caller waits; next line runs only after work() returns</span>
<span class="n">serial</span><span class="o">.</span><span class="n">sync</span> <span class="p">{</span> <span class="nf">moreWork</span><span class="p">()</span> <span class="p">}</span>
<span class="c1">// work() always finishes before moreWork() starts</span>
</code></pre></div></div>

<p>Useful when you need a result immediately and can afford to block the calling thread. Deadlocks if you call <code class="language-plaintext highlighter-rouge">serial.sync</code> from a task already running on <code class="language-plaintext highlighter-rouge">serial</code>.</p>

<hr />

<p><strong>Serial + async</strong> — caller returns immediately, queue still runs one task at a time.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">serial</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span> <span class="nf">work</span><span class="p">()</span> <span class="p">}</span>
<span class="n">serial</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span> <span class="nf">moreWork</span><span class="p">()</span> <span class="p">}</span>
<span class="c1">// caller continues; work() finishes before moreWork() starts, but caller doesn't wait</span>
</code></pre></div></div>

<p>The classic pattern for thread-safe state mutation without blocking. All mutations are serialized; no locks needed.</p>

<hr />

<p><strong>Concurrent + sync</strong> — caller blocks until <em>its</em> task finishes; other tasks on the queue may overlap.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">concurrentQueue</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span> <span class="nf">longTask</span><span class="p">()</span> <span class="p">}</span>  <span class="c1">// already running</span>
<span class="n">concurrentQueue</span><span class="o">.</span><span class="n">sync</span>  <span class="p">{</span> <span class="nf">quickTask</span><span class="p">()</span> <span class="p">}</span> <span class="c1">// caller blocks for quickTask(); longTask() may still be running</span>
</code></pre></div></div>

<p>Rarely the right tool. If you need the result synchronously, you probably want a serial queue or just inline the work.</p>

<hr />

<p><strong>Concurrent + async</strong> — caller returns immediately, tasks may run in parallel.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">item</span> <span class="k">in</span> <span class="n">items</span> <span class="p">{</span>
    <span class="n">concurrent</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span> <span class="nf">process</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// all items may process simultaneously</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Maximum throughput for independent work. Order of completion is not guaranteed.</p>

<hr />

<p><strong>The mental model:</strong></p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Sync</th>
      <th>Async</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Serial</strong></td>
      <td>blocks caller, ordered</td>
      <td>non-blocking, ordered</td>
    </tr>
    <tr>
      <td><strong>Concurrent</strong></td>
      <td>blocks caller, may overlap others</td>
      <td>non-blocking, may overlap</td>
    </tr>
  </tbody>
</table>

<p>Sync/async is about the <em>submitter’s</em> thread. Serial/concurrent is about the <em>queue’s</em> parallelism. They compose independently.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="gcd" /><category term="concurrency" /><category term="threading" /><category term="ios" /><summary type="html"><![CDATA[Two independent axes, four combinations. They’re easy to conflate because the names sound similar, but they control completely different things.]]></summary></entry><entry><title type="html">OSAllocatedUnfairLock — A Lock That Owns Its State</title><link href="https://www.eliaszsawicki.com/osallocatedunfairlock/" rel="alternate" type="text/html" title="OSAllocatedUnfairLock — A Lock That Owns Its State" /><published>2026-05-25T00:00:00+00:00</published><updated>2026-05-25T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/osallocatedunfairlock</id><content type="html" xml:base="https://www.eliaszsawicki.com/osallocatedunfairlock/"><![CDATA[<p><code class="language-plaintext highlighter-rouge">OSAllocatedUnfairLock</code> is a low-level mutex added in iOS 16 / macOS 13. Unlike <code class="language-plaintext highlighter-rouge">NSLock</code>, it’s a <strong>struct</strong> — but you can copy it freely because the actual lock storage is heap-allocated (hence “allocated”). The struct is just a handle.</p>

<p>The defining design choice: the protected value lives <em>inside</em> the lock, not alongside it. You get it only by going through <code class="language-plaintext highlighter-rouge">withLock</code>:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">counter</span> <span class="o">=</span> <span class="kt">OSAllocatedUnfairLock</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span>

<span class="n">counter</span><span class="o">.</span><span class="n">withLock</span> <span class="p">{</span> <span class="n">value</span> <span class="k">in</span>
    <span class="n">value</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">}</span>

<span class="k">let</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="n">counter</span><span class="o">.</span><span class="n">withLock</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
</code></pre></div></div>

<p>Compare that to <code class="language-plaintext highlighter-rouge">NSLock</code>, where nothing stops you from reading the value without locking:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">let</span> <span class="nv">lock</span> <span class="o">=</span> <span class="kt">NSLock</span><span class="p">()</span>

<span class="n">lock</span><span class="o">.</span><span class="nf">lock</span><span class="p">()</span>
<span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">lock</span><span class="o">.</span><span class="nf">unlock</span><span class="p">()</span>

<span class="nf">print</span><span class="p">(</span><span class="n">count</span><span class="p">)</span> <span class="c1">// compiles fine — lock is bypassed entirely</span>
</code></pre></div></div>

<p><strong>“Unfair”</strong> means threads don’t get the lock in arrival order. The OS picks freely, which avoids the overhead of maintaining a queue. In practice this gives it much lower latency than <code class="language-plaintext highlighter-rouge">NSLock</code> for uncontended cases.</p>

<p><strong>It’s non-recursive.</strong> Calling <code class="language-plaintext highlighter-rouge">withLock</code> again from inside <code class="language-plaintext highlighter-rouge">withLock</code> on the same lock is a deadlock, not a re-entry.</p>

<p><strong><code class="language-plaintext highlighter-rouge">Sendable</code> conformance</strong></p>

<p><code class="language-plaintext highlighter-rouge">OSAllocatedUnfairLock</code> is <code class="language-plaintext highlighter-rouge">Sendable</code> when <code class="language-plaintext highlighter-rouge">State: Sendable</code>. Because the state is only reachable through <code class="language-plaintext highlighter-rouge">withLock</code>, the compiler can verify that sharing the lock across concurrency boundaries is safe — no <code class="language-plaintext highlighter-rouge">@unchecked Sendable</code> needed.</p>

<p>This makes it a clean fit for Swift 6 strict concurrency. A class that holds mutable state and an <code class="language-plaintext highlighter-rouge">NSLock</code> forces you to suppress the warning yourself:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Swift 6 — compiler complains</span>
<span class="kd">class</span> <span class="kt">Counter</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">value</span> <span class="o">=</span> <span class="mi">0</span>        <span class="c1">// ⚠️ stored property 'value' of 'Sendable'-conforming class is mutable</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">lock</span> <span class="o">=</span> <span class="kt">NSLock</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">OSAllocatedUnfairLock</code> the state is inside the lock, so the compiler is satisfied without any suppression:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">Counter</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">lock</span> <span class="o">=</span> <span class="kt">OSAllocatedUnfairLock</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">// ✅</span>

    <span class="kd">func</span> <span class="nf">increment</span><span class="p">()</span> <span class="p">{</span> <span class="n">lock</span><span class="o">.</span><span class="n">withLock</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">+=</span> <span class="mi">1</span> <span class="p">}</span> <span class="p">}</span>
    <span class="kd">func</span> <span class="nf">value</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span> <span class="n">lock</span><span class="o">.</span><span class="n">withLock</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Takeaway:</strong> reach for <code class="language-plaintext highlighter-rouge">OSAllocatedUnfairLock</code> when you need a fast, struct-friendly mutex and want the compiler to make it hard to access shared state without holding the lock.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="concurrency" /><category term="threading" /><category term="ios" /><summary type="html"><![CDATA[OSAllocatedUnfairLock is a low-level mutex added in iOS 16 / macOS 13. Unlike NSLock, it’s a struct — but you can copy it freely because the actual lock storage is heap-allocated (hence “allocated”). The struct is just a handle.]]></summary></entry><entry><title type="html">[weak self] + guard let self in a Task — A Pattern Worth Dropping</title><link href="https://www.eliaszsawicki.com/weak-self-task-antipattern/" rel="alternate" type="text/html" title="[weak self] + guard let self in a Task — A Pattern Worth Dropping" /><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/weak-self-task-antipattern</id><content type="html" xml:base="https://www.eliaszsawicki.com/weak-self-task-antipattern/"><![CDATA[<p>With completion handlers, <code class="language-plaintext highlighter-rouge">[weak self]</code> + <code class="language-plaintext highlighter-rouge">guard let self</code> was a clean pattern. The closure was stored externally, created a retain cycle, and the body was short and synchronous. Weak capture prevented the cycle; the guard was a cheap nil check at the top.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Callbacks — this made sense</span>
<span class="n">service</span><span class="o">.</span><span class="n">fetch</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">result</span> <span class="k">in</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
    <span class="k">self</span><span class="o">.</span><span class="nf">update</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="c1">// synchronous, done immediately</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The pattern got carried into Swift concurrency by reflex, and it’s worth questioning it there.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">load</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">Task</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetchData</span><span class="p">()</span>
        <span class="k">let</span> <span class="nv">processed</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">process</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="nf">update</span><span class="p">(</span><span class="n">processed</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">guard let self</code> at the top promotes the weak reference back to a strong one immediately. Self is now strongly retained for the entire task — across every suspension point, for however long the task runs. The weak capture only did work for the instant before the guard.</p>

<p>More importantly: <code class="language-plaintext highlighter-rouge">Task { }</code> doesn’t get stored by anyone externally the way a completion handler does, so there’s rarely a retain cycle to break in the first place.</p>

<p><strong>What to do instead</strong></p>

<p>If the task represents work that belongs to the object, just capture strongly:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">load</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">Task</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetchData</span><span class="p">()</span>
        <span class="nf">update</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1">// self kept alive for as long as the work runs — that's fine</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If you want to discard results when the object is gone by the time the async work finishes, check at the point where it matters — after the await:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">load</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">Task</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
        <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetchData</span><span class="p">()</span> <span class="c1">// do the work regardless</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span> <span class="c1">// discard if object is gone by now</span>
        <span class="nf">update</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="concurrency" /><category term="async" /><category term="ios" /><summary type="html"><![CDATA[With completion handlers, [weak self] + guard let self was a clean pattern. The closure was stored externally, created a retain cycle, and the body was short and synchronous. Weak capture prevented the cycle; the guard was a cheap nil check at the top.]]></summary></entry><entry><title type="html">Task Scheduling Order and Actor Isolation</title><link href="https://www.eliaszsawicki.com/task-weak-self-actor-timing/" rel="alternate" type="text/html" title="Task Scheduling Order and Actor Isolation" /><published>2026-05-11T00:00:00+00:00</published><updated>2026-05-11T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/task-weak-self-actor-timing</id><content type="html" xml:base="https://www.eliaszsawicki.com/task-weak-self-actor-timing/"><![CDATA[<p>This is an academic example to illustrate scheduling order — not a pattern worth copying. Using <code class="language-plaintext highlighter-rouge">[weak self]</code> with <code class="language-plaintext highlighter-rouge">guard let self</code> inside a <code class="language-plaintext highlighter-rouge">Task</code> is questionable in practice: <code class="language-plaintext highlighter-rouge">guard let self</code> immediately promotes the weak reference back to a strong one, so you’re not really guarding against anything meaningful for the duration of the task body. In real code, prefer structured concurrency, just let the task capture <code class="language-plaintext highlighter-rouge">self</code> strongly or use weak and check if it exists after suspension points.</p>

<p>That said, the scheduling behavior this reveals is worth understanding.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">ViewModel</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">work</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="nf">print</span><span class="p">(</span><span class="s">"No self"</span><span class="p">);</span> <span class="k">return</span> <span class="p">}</span>
            <span class="nf">doWork</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">var</span> <span class="nv">vm</span><span class="p">:</span> <span class="kt">ViewModel</span><span class="p">?</span> <span class="o">=</span> <span class="kt">ViewModel</span><span class="p">()</span>
<span class="n">vm</span><span class="p">?</span><span class="o">.</span><span class="nf">work</span><span class="p">()</span>
<span class="n">vm</span> <span class="o">=</span> <span class="kc">nil</span>
</code></pre></div></div>

<p><strong>Without <code class="language-plaintext highlighter-rouge">@MainActor</code>:</strong> <code class="language-plaintext highlighter-rouge">work()</code> is nonisolated, so the task runs on the cooperative thread pool. It may start executing before the main thread processes <code class="language-plaintext highlighter-rouge">vm = nil</code>. Self is still alive — the guard passes silently.</p>

<p><strong>With <code class="language-plaintext highlighter-rouge">@MainActor in</code>:</strong> the task is queued on the main actor. The main thread finishes its current job first — including <code class="language-plaintext highlighter-rouge">vm = nil</code> — then picks up the task. By that point self is nil, and the guard catches it.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Task</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>           <span class="c1">// thread pool — races with vm = nil on main thread</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span> <span class="c1">// self may still be alive</span>
<span class="p">}</span>

<span class="kt">Task</span> <span class="p">{</span> <span class="kd">@MainActor</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>  <span class="c1">// queued after current main actor job completes</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>   <span class="c1">// vm = nil has already run</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The point isn’t weak self — it’s that <strong>a task bound to the main actor can only run after the main actor finishes its current work</strong>. Switching actor context changes the scheduling order, which changes what state the world is in when the task body starts.</p>

<p>The rule: <code class="language-plaintext highlighter-rouge">@MainActor in</code> doesn’t change capture semantics — it changes when the task runs relative to everything else on the main actor.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="concurrency" /><category term="async" /><category term="ios" /><category term="mainactor" /><summary type="html"><![CDATA[This is an academic example to illustrate scheduling order — not a pattern worth copying. Using [weak self] with guard let self inside a Task is questionable in practice: guard let self immediately promotes the weak reference back to a strong one, so you’re not really guarding against anything meaningful for the duration of the task body. In real code, prefer structured concurrency, just let the task capture self strongly or use weak and check if it exists after suspension points.]]></summary></entry><entry><title type="html">Task.yield() — Cooperative Pause, Not a Background Escape</title><link href="https://www.eliaszsawicki.com/task-yield/" rel="alternate" type="text/html" title="Task.yield() — Cooperative Pause, Not a Background Escape" /><published>2026-05-04T00:00:00+00:00</published><updated>2026-05-04T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/task-yield</id><content type="html" xml:base="https://www.eliaszsawicki.com/task-yield/"><![CDATA[<p><code class="language-plaintext highlighter-rouge">await Task.yield()</code> suspends the current task and lets the scheduler run other pending work before resuming.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">crunchNumbers</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..&lt;</span><span class="mi">1_000_000</span> <span class="p">{</span>
        <span class="nf">process</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">i</span><span class="o">.</span><span class="nf">isMultiple</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="mi">1000</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">await</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">yield</span><span class="p">()</span> <span class="c1">// give other tasks a turn</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Without the yield, a long CPU-bound loop holds the thread uninterrupted. Other tasks in the same cooperative thread pool can starve. Yielding periodically makes the loop cooperative.</p>

<p><strong>It also acts as a cancellation checkpoint:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">crunchNumbers</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..&lt;</span><span class="mi">1_000_000</span> <span class="p">{</span>
        <span class="k">try</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">checkCancellation</span><span class="p">()</span>
        <span class="nf">process</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">i</span><span class="o">.</span><span class="nf">isMultiple</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="mi">1000</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">await</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">yield</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After resuming from <code class="language-plaintext highlighter-rouge">yield</code>, the task checks cancellation on the next <code class="language-plaintext highlighter-rouge">checkCancellation()</code> call. Without any suspension point, a cancelled task runs to completion anyway.</p>

<hr />

<p><strong>Red flags</strong></p>

<p><code class="language-plaintext highlighter-rouge">Task.yield()</code> is not a background escape. On <code class="language-plaintext highlighter-rouge">@MainActor</code>, the task resumes back on the main thread. The main thread is freed only for the brief window between suspend and reschedule — not long enough for heavy computation to stop being a problem.</p>

<p>There’s also no fairness guarantee — if nothing else is waiting, <code class="language-plaintext highlighter-rouge">yield</code> resumes immediately. It’s a hint to the scheduler, not a timed pause.</p>

<p><strong>The testing trap</strong></p>

<p><code class="language-plaintext highlighter-rouge">Task.yield()</code> sometimes shows up in tests to “flush” unstructured tasks:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testSomething</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">result</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="kt">Task</span> <span class="p">{</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">42</span> <span class="p">}</span>
    <span class="k">await</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">yield</span><span class="p">()</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span> <span class="c1">// passes... sometimes</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is a code smell. <code class="language-plaintext highlighter-rouge">yield</code> gives the scheduled task a chance to run, but offers no delivery guarantee — the test is racing the scheduler. It can pass locally and fail on CI under load.</p>

<p>The real fix is to not lose the handle:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">testSomething</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">result</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">let</span> <span class="nv">task</span> <span class="o">=</span> <span class="kt">Task</span> <span class="p">{</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">42</span> <span class="p">}</span>
    <span class="k">await</span> <span class="n">task</span><span class="o">.</span><span class="n">value</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">42</span><span class="p">)</span> <span class="c1">// deterministic</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If you see <code class="language-plaintext highlighter-rouge">Task.yield()</code> in a test, it’s almost always papering over an unstructured task that should either be awaited directly or replaced with structured concurrency.</p>

<p>The rule: use <code class="language-plaintext highlighter-rouge">yield</code> to keep CPU-bound loops cooperative and cancellation-responsive. In tests, treat it as a red flag.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="concurrency" /><category term="async" /><category term="ios" /><summary type="html"><![CDATA[await Task.yield() suspends the current task and lets the scheduler run other pending work before resuming.]]></summary></entry></feed>