<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/"
>

<channel>
	<title>Coding &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/categories/coding/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Mon, 23 Mar 2026 01:03:54 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>Coding &#8211; Wade Tregaskis</title>
	<link>https://wadetregaskis.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">226351702</site>	<item>
		<title>SSH waits for background jobs to exit only in non-pseudo-terminal mode</title>
		<link>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/</link>
					<comments>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 22 Mar 2026 23:40:42 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[pseudo-terminal]]></category>
		<category><![CDATA[SSH]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8867</guid>

					<description><![CDATA[Ugh, this one caused me days of head-scratching. If you run that command sequence, you&#8217;ll find that it makes the SSH connection and then exits immediately &#8211; and it kills the background job (sleep 15) as it exits. Which I find intuitive if only because that&#8217;s obviously how it&#8217;s always worked. But now run it&#8230; <a class="read-more-link" href="https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Ugh, this one caused me days of head-scratching.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$ ssh somehost
somehost$ sleep 15 &amp;
somehost$ exit</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">$ </span><span style="color: #A31515">ssh</span><span style="color: #000000"> </span><span style="color: #A31515">somehost</span></span>
<span class="line"><span style="color: #000000">somehost$ </span><span style="color: #A31515">sleep</span><span style="color: #000000"> </span><span style="color: #098658">15</span><span style="color: #000000"> &amp;</span></span>
<span class="line"><span style="color: #000000">somehost$ </span><span style="color: #A31515">exit</span></span></code></pre></div>



<p>If you run that command sequence, you&#8217;ll find that it makes the SSH connection and then exits immediately &#8211; and it kills the background job (<code>sleep 15</code>) as it exits.  Which I find intuitive if only because that&#8217;s obviously how it&#8217;s always worked.</p>



<p>But now run it this way:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$ ssh somehost 'sleep 15 &amp;'</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">$ </span><span style="color: #A31515">ssh</span><span style="color: #000000"> </span><span style="color: #A31515">somehost</span><span style="color: #000000"> </span><span style="color: #A31515">&#39;sleep 15 &amp;&#39;</span></span></code></pre></div>



<p>Now it makes the SSH connection, <em>waits fifteen seconds</em>, and only <em>then</em> exits.</p>



<p>This subtle difference matters a lot if you&#8217;re e.g. writing an automation system that happens to sometimes spawn background jobs on remote machines via SSH, since it can lead to SSH commands hanging (potentially forever, depending on the nature of the background jobs).</p>



<p>And it&#8217;s <em>very</em> hard to debug, because no matter what debugging aids you put into your SSH session&#8217;s remote command &#8211; e.g. an explicit <code>exit 0</code> at the end, <code>set -xv</code>, or <code>echo 'Okay, going away now!'</code> as the last command &#8211; it&#8217;ll still just hang instead of completing.</p>



<p>Turns out this is because when run <em>without</em> a remote command, SSH implicitly creates a pseudo-terminal.  And the <em>pseudo-terminal</em> provides the behaviour of killing all subprocesses (including un-detached background jobs) when the main process ends.</p>



<p>When you run SSH <em>with</em> a remote command, SSH does <em>not</em> create a pseudo-terminal.  I&#8217;m guessing technically what it&#8217;s doing is waiting for the remote side to close the connection, which the remote side (sshd) won&#8217;t do until <em>all</em> child processes have exited (or at least closed their stdin, stdout, and stderr?).</p>



<p>One workaround is to use the <code>-t</code> argument to SSH, to force creation of a pseudo-terminal.  The downside is that remote programs may then assume an interactive session, so they may prompt for input in cases where they wouldn&#8217;t otherwise.  That could cause misalignment between what you write through to the remote command and what it&#8217;s expecting input for, or may cause a hang (if you don&#8217;t write anything but keep the remote side&#8217;s stdin open), or may cause the remote command to abort because it detects the end of stdin.</p>



<p>Another option is to manually kill all background jobs before exiting the main command, with e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>jobs -p | egrep '^\d' | xargs kill</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #795E26">jobs</span><span style="color: #000000"> </span><span style="color: #0000FF">-p</span><span style="color: #000000"> | egrep </span><span style="color: #A31515">&#39;^\d&#39;</span><span style="color: #000000"> | xargs </span><span style="color: #A31515">kill</span></span></code></pre></div>



<p>…or just kill <em>all</em> subprocesses:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">pkill </span><span style="color: #0000FF">-P</span><span style="color: #000000"> </span><span style="color: #0000FF">$$</span></span></code></pre></div>



<p>Or, if you can precisely control where you create background jobs (<em>and</em> are confident they don&#8217;t spawn background jobs recursively), you can do:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>some background command &amp;
BACKGROUND_PID=$!

… # Rest of script.

kill ${BACKGROUND_PID}
# *Now* we can exit without hanging.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">some </span><span style="color: #A31515">background</span><span style="color: #000000"> </span><span style="color: #A31515">command</span><span style="color: #000000"> &amp;</span></span>
<span class="line"><span style="color: #001080">BACKGROUND_PID</span><span style="color: #000000">=</span><span style="color: #0000FF">$!</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">… </span><span style="color: #008000"># Rest of script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">kill</span><span style="color: #000000"> ${BACKGROUND_PID}</span></span>
<span class="line"><span style="color: #008000"># *Now* we can exit without hanging.</span></span></code></pre></div>



<p>Just beware with this more precise approach that it&#8217;s more fragile &#8211; subprocesses could get spawned in ways you didn&#8217;t anticipate (if not now, then maybe in future versions of your code / production environment), and that sometimes the PID returned is effectively invalid because it&#8217;s merely the PID of some transient subprocess anyway (not the <em>long-lived</em> subprocess(es)).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8867</post-id>	</item>
		<item>
		<title>&#8220;\r\n&#8221; is one Character in Swift</title>
		<link>https://wadetregaskis.com/rn-is-one-character-in-swift/</link>
					<comments>https://wadetregaskis.com/rn-is-one-character-in-swift/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 01:43:49 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[isNewline]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Unicode]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8790</guid>

					<description><![CDATA[From the department of “how did I not realise this sooner?!”: Yes, Swift treats the two bytes &#8220;\r\n&#8221; as a single character.  This is actually super convenient a lot of the time, because it means algorithms that look for line breaks with isNewline just work, even on “Windows”-style text.  Otherwise, you’d have to explicitly look&#8230; <a class="read-more-link" href="https://wadetregaskis.com/rn-is-one-character-in-swift/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>From the department of “how did I not realise this sooner?!”:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">  </span><span style="color: #098658">1</span><span style="color: #000000">&gt; </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\r</span><span style="color: #A31515">&quot;</span><span style="color: #000000">.</span><span style="color: #001080">count</span></span>
<span class="line"><span style="color: #000000">$R0: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">1</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #098658">2</span><span style="color: #000000">&gt; </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\n</span><span style="color: #A31515">&quot;</span><span style="color: #000000">.</span><span style="color: #001080">count</span><span style="color: #000000"> </span></span>
<span class="line"><span style="color: #000000">$R1: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">1</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #098658">3</span><span style="color: #000000">&gt; </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\r\n</span><span style="color: #A31515">&quot;</span><span style="color: #000000">.</span><span style="color: #001080">count</span><span style="color: #000000"> </span></span>
<span class="line"><span style="color: #000000">$R2: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">1</span></span></code></pre></div>



<p>Yes, Swift treats the two bytes &#8220;\r\n&#8221; as a <em>single</em> character.  This is actually super convenient a lot of the time, because it means algorithms that look for line breaks with <code>isNewline</code> just work, even on “Windows”-style text.  Otherwise, you’d have to explicitly look for two <code>isNewline</code> characters in sequence <em>and</em> check if they are exactly &#8220;\r\n&#8221;.</p>



<p>But it does lead to some potentially surprising side-effects, like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000"> </span><span style="color: #098658">4</span><span style="color: #000000">&gt; x.</span><span style="color: #001080">unicodeScalars</span><span style="color: #000000">.</span><span style="color: #001080">count</span></span>
<span class="line"><span style="color: #000000">$R3: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">2</span></span>
<span class="line"><span style="color: #000000"> </span><span style="color: #098658">5</span><span style="color: #000000">&gt; </span><span style="color: #267F99">Array</span><span style="color: #000000">(x.</span><span style="color: #001080">unicodeScalars</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">$R4: &#91;</span><span style="color: #267F99">String</span><span style="color: #000000">.</span><span style="color: #267F99">UnicodeScalarView</span><span style="color: #000000">.</span><span style="color: #267F99">Element</span><span style="color: #000000">&#93; = </span><span style="color: #098658">2</span><span style="color: #000000"> values {</span></span>
<span class="line"><span style="color: #000000">  &#91;</span><span style="color: #098658">0</span><span style="color: #000000">&#93; = U&#39;\r&#39;</span></span>
<span class="line"><span style="color: #000000">  &#91;</span><span style="color: #098658">1</span><span style="color: #000000">&#93; = U&#39;\n&#39;</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This isn’t surprising if you’re pretty familiar with how Unicode actually works &#8211; starting with the difference between graphemes (approximately what Swift calls a Character) and &#8220;scalars&#8221;, but I suspect it’ll catch some people out.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/rn-is-one-character-in-swift/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8790</post-id>	</item>
		<item>
		<title>Fading audio with AVPlayer</title>
		<link>https://wadetregaskis.com/fading-audio-with-avplayer/</link>
					<comments>https://wadetregaskis.com/fading-audio-with-avplayer/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 10 Dec 2025 16:29:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[AVAudioMix]]></category>
		<category><![CDATA[AVAudioMixInputParameters]]></category>
		<category><![CDATA[AVMutableAudioMix]]></category>
		<category><![CDATA[AVMutableAudioMixInputParameters]]></category>
		<category><![CDATA[AVPlayer]]></category>
		<category><![CDATA[fade]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8630</guid>

					<description><![CDATA[AVPlayer doesn&#8217;t provide a built-in way to fade in or out. I previously described how you achieve a video fade-in (or out) using general CoreAnimation layer animation, as part of making a macOS screen saver. Now let&#8217;s tackle the audio. I&#8217;m not certain what curve this implements, but to my ears it doesn&#8217;t sound quite&#8230; <a class="read-more-link" href="https://wadetregaskis.com/fading-audio-with-avplayer/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code>AVPlayer</code> doesn&#8217;t provide a built-in way to fade in or out.  I previously described how you achieve a <em>video</em> fade-in (or out) using general CoreAnimation layer animation, as <a href="https://wadetregaskis.com/how-to-make-a-macos-screen-saver/#Bonus_topic_fading_in" data-wpel-link="internal">part of making a macOS screen saver</a>.  Now let&#8217;s tackle the audio.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">AVPlayer</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">fadeAudio</span><span style="color: #000000">(</span><span style="color: #795E26">from</span><span style="color: #000000"> </span><span style="color: #001080">startVolume</span><span style="color: #000000">: </span><span style="color: #267F99">Float</span><span style="color: #000000">, </span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">endVolume</span><span style="color: #000000">: </span><span style="color: #267F99">Float</span><span style="color: #000000">, </span><span style="color: #795E26">duration</span><span style="color: #000000">: </span><span style="color: #267F99">Double</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> audioMix = </span><span style="color: #795E26">AVMutableAudioMix</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        audioMix.</span><span style="color: #001080">inputParameters</span><span style="color: #000000"> = (player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">?.</span><span style="color: #001080">tracks</span><span style="color: #000000"> ?? [])</span></span>
<span class="line"><span style="color: #000000">                                    .</span><span style="color: #795E26">compactMap</span><span style="color: #000000">(\.</span><span style="color: #001080">assetTrack</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">                                    .</span><span style="color: #795E26">filter</span><span style="color: #000000">({ </span><span style="color: #0000FF">$0</span><span style="color: #000000">.</span><span style="color: #001080">mediaType</span><span style="color: #000000"> == .</span><span style="color: #001080">audio</span><span style="color: #000000"> })</span></span>
<span class="line"><span style="color: #000000">                                    .</span><span style="color: #795E26">map</span><span style="color: #000000"> { track </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> currentTime = player.</span><span style="color: #795E26">currentTime</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> parameters = </span><span style="color: #795E26">AVMutableAudioMixInputParameters</span><span style="color: #000000">(</span><span style="color: #795E26">track</span><span style="color: #000000">: track)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            parameters.</span><span style="color: #795E26">setVolumeRamp</span><span style="color: #000000">(</span><span style="color: #795E26">fromStartVolume</span><span style="color: #000000">: startVolume,</span></span>
<span class="line"><span style="color: #000000">                                     </span><span style="color: #795E26">toEndVolume</span><span style="color: #000000">: endVolume,</span></span>
<span class="line"><span style="color: #000000">                                     </span><span style="color: #795E26">timeRange</span><span style="color: #000000">: </span><span style="color: #795E26">CMTimeRange</span><span style="color: #000000">(</span><span style="color: #795E26">start</span><span style="color: #000000">: currentTime,</span></span>
<span class="line"><span style="color: #000000">                                                            </span><span style="color: #795E26">duration</span><span style="color: #000000">: </span><span style="color: #795E26">CMTime</span><span style="color: #000000">(</span><span style="color: #795E26">seconds</span><span style="color: #000000">: duration,</span></span>
<span class="line"><span style="color: #000000">                                                                             </span><span style="color: #795E26">preferredTimescale</span><span style="color: #000000">: currentTime.</span><span style="color: #001080">timeScale</span><span style="color: #000000">)))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> parameters</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">?.</span><span style="color: #001080">audioMix</span><span style="color: #000000"> = audioMix</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>I&#8217;m not certain what curve this implements, but to my ears it doesn&#8217;t sound quite as harsh as a naive linear ramp, so perhaps it&#8217;s an S-curve or similar.</p>



<h2 class="wp-block-heading">Edge cases not handled</h2>



<h3 class="wp-block-heading">Where there&#8217;s less than <code>duration</code> time left in the track(s).</h3>



<p>How you want to handle that might vary depending on context. e.g. you could clamp the duration to the remaining duration (but you have to think about whether your individual tracks all have the same duration, whether they match the duration of the overall playback item, and whether they&#8217;re all aligned within the playback sequence), or wrap it around to the beginning (if you&#8217;re looping), or carry the fade through to the next item (if you&#8217;re playing a sequence of items), etc. Alas this must be left as an exercise to you, the reader.</p>



<h3 class="wp-block-heading">Starting a fade while another one is still in progress.</h3>



<p>It will halt the previous fade, immediately jump to <code>startVolume</code>, and perform the new fade. If you know that a prior fade is in progress you could potentially extrapolate the current volume and start from there instead (though beware of non-linear ramping).</p>



<h3 class="wp-block-heading">If you move the playhead backwards (e.g. skimming, or looped playback).</h3>



<p>The mix will stay in place, and result in wonky volume levels on the subsequent plays through. To work around that, you can add:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">?.</span><span style="color: #001080">audioMix</span><span style="color: #000000"> = </span><span style="color: #0000FF">nil</span></span>
<span class="line"><span style="color: #000000">player.</span><span style="color: #001080">volume</span><span style="color: #000000"> = endVolume</span></span></code></pre></div>



<p>…wherever you restart playback at the beginning or move playback earlier than the end of the fade.</p>



<h2 class="wp-block-heading">Future work?</h2>



<p>You <em>can</em> work around these limitations by doing a timer-based fade (i.e. <code>player.volume += smallIncrement</code> at regular, short intervals). However, the problem with that approach is that it&#8217;s not synchronised to actual playback &#8211; e.g. if the audio is paused, stutters, or faces an initial loading delay, your fade won&#8217;t wait for it, potentially resulting in no fade at all (e.g. it takes five seconds to buffer the audio before playback starts, at which point your five second &#8220;fade&#8221; has run to completion, so your audio starts playing abruptly at full volume).</p>



<p>There&#8217;s very likely a third option that addresses <em>all</em> these shortcomings, but I explored that a bit and concluded that it&#8217;d be a lot more work.  If someone wants to explore that all the way, I&#8217;d be interested to see the result.  But for many purposes the above code is quite sufficient.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/fading-audio-with-avplayer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8630</post-id>	</item>
		<item>
		<title>How to loop video in AVPlayer</title>
		<link>https://wadetregaskis.com/how-to-loop-video-in-avplayer/</link>
					<comments>https://wadetregaskis.com/how-to-loop-video-in-avplayer/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 09 Dec 2025 17:42:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[AVPlayer]]></category>
		<category><![CDATA[AVPlayerItem]]></category>
		<category><![CDATA[AVPlayerItemDidPlayToEndTime]]></category>
		<category><![CDATA[AVPlayerLooper]]></category>
		<category><![CDATA[AVQueuePlayer]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8625</guid>

					<description><![CDATA[This is pretty rudimentary, but apparently our robot overlords need me to write this post because many of them suggested some truly bizarre approaches, some of which don&#8217;t work at all. If you&#8217;re using AVQueuePlayer, then just use AVPlayerLooper. Easy. But if for some reason you want to use AVPlayer specifically (e.g. you need to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/how-to-loop-video-in-avplayer/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is pretty rudimentary, but apparently our robot overlords need me to write this post because many of them suggested some truly bizarre approaches, some of which don&#8217;t work at all.</p>



<p>If you&#8217;re using <code><a href="https://developer.apple.com/documentation/avfoundation/avqueueplayer" data-wpel-link="external" target="_blank" rel="external noopener">AVQueuePlayer</a></code>, then just use <code><a href="https://developer.apple.com/documentation/avfoundation/avplayerlooper" data-wpel-link="external" target="_blank" rel="external noopener">AVPlayerLooper</a></code>.  Easy.  But if for some reason you want to use <code>AVPlayer</code> specifically (e.g. you need to do additional things anyway when playback loops back around), read on.</p>



<p><code>AVPlayer</code> itself doesn&#8217;t really help you here &#8211; it&#8217;s <code>AVPlayerItem</code> that you need to look at, as it has several notifications associated with it that can be very useful &#8211; most relevant here is<a href="https://developer.apple.com/documentation/avfoundation/avplayeritem/didplaytoendtimenotification" data-wpel-link="external" target="_blank" rel="external noopener"> </a>the <a href="https://developer.apple.com/documentation/avfoundation/avplayeritem/didplaytoendtimenotification" data-wpel-link="external" target="_blank" rel="external noopener">didPlayToEndTimeNotification</a>.  Simply observe that and restart the player explicitly, like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> player = </span><span style="color: #795E26">AVPlayer</span><span style="color: #000000">(…)  </span><span style="color: #008000">// You&#39;ll need to keep a reference to this somewhere, for use in the notification handler, as the notification doesn&#39;t provide it.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">void </span><span style="color: #795E26">startPlayer</span><span style="color: #000000">() {  </span><span style="color: #008000">// Or wherever / however you start playback.</span></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">playedToEnd</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">name</span><span style="color: #000000">: .</span><span style="color: #001080">AVPlayerItemDidPlayToEndTime</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">object</span><span style="color: #000000">: player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    player.</span><span style="color: #795E26">play</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">} </span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">playedToEnd</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">assert</span><span style="color: #000000">(player.</span><span style="color: #001080">currentItem</span><span style="color: #000000"> == notification.</span><span style="color: #001080">object</span><span style="color: #000000"> as? AVPlayerItem, </span><span style="color: #A31515">&quot;AVPlayer </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">player</span><span style="color: #0000FF">)</span><span style="color: #A31515">&#39;s current item (</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">player.</span><span style="color: #001080">currentItem</span><span style="color: #0000FF">)</span><span style="color: #A31515">) doesn&#39;t match the one in the notification object (</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">notification.</span><span style="color: #001080">object</span><span style="color: #0000FF">)</span><span style="color: #A31515">).&quot;</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    player.</span><span style="color: #795E26">seek</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000">: .</span><span style="color: #001080">zero</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    player.</span><span style="color: #795E26">play</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Again, considering using <code>AVQueuePlayer</code> if possible, but the above works too.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-to-loop-video-in-avplayer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8625</post-id>	</item>
		<item>
		<title>How to make a macOS screen saver</title>
		<link>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/</link>
					<comments>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 06 Dec 2025 22:46:22 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[com.apple.screensaver.willstop]]></category>
		<category><![CDATA[SaveHollywood]]></category>
		<category><![CDATA[Screen saver]]></category>
		<category><![CDATA[ScreenSaverView]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8580</guid>

					<description><![CDATA[First, make sure you really want to. macOS&#8217;s screen saver system is absurdly buggy and broken. It&#8217;s frustrating to work with and very difficult to make work right. If you&#8217;re determined, read on. Screen savers are basically just applications. Same bundle format and structure, just with a &#8220;saver&#8221; extension instead of &#8220;app&#8221;. Setting up the&#8230; <a class="read-more-link" href="https://wadetregaskis.com/how-to-make-a-macos-screen-saver/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>First, make sure you <em>really</em> want to.  macOS&#8217;s screen saver system is absurdly buggy and broken.  It&#8217;s frustrating to work with and very difficult to make work right.</p>



<p>If you&#8217;re determined, read on.</p>



<p>Screen savers are basically just applications.  Same bundle format and structure, just with a &#8220;saver&#8221; extension instead of &#8220;app&#8221;.</p>



<h2 class="wp-block-heading">Setting up the Xcode project</h2>



<p>In Xcode, create a new project using the <code>Screen Saver</code> template.</p>



<p>Delete the Objective-C code &amp; header files it creates by default (unless, I suppose, you want to write your screen saver in Objective-C &#8211; woo, retro! 😆).</p>



<p>You need to import the ScreenSaver module (framework), subclass ScreenSaverView, and implement a couple of method overrides.  Here&#8217;s the basic skeleton:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import ScreenSaver

class MyScreenSaver: ScreenSaverView {
    override init?(frame: NSRect, isPreview: Bool) {
        super.init(frame: frame, isPreview: isPreview)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        // TODO
    }

    override func startAnimation() {
        super.startAnimation()

        // TODO
    }

    override func animateOneFrame() {  // Optional.
        // TODO
    }

    override func stopAnimation() { //  Only for the live preview in System Settings.
        // TODO

        super.stopAnimation()
    }

    override var hasConfigureSheet: Bool {
        true
    }

    private var configureSheetController: ConfigureSheetController?

    override var configureSheet: NSWindow? {
        configureSheetController = ConfigureSheetController(windowNibName: "ConfigureSheet")
        return configureSheetController?.window
    }
}

class ConfigureSheetController: NSWindowController {
    override var windowNibName: NSNib.Name? {
        return "ConfigureSheet"
    }

    override func windowDidLoad() {
        super.windowDidLoad()

        // TODO
    }

    @IBAction func okButtonClicked(_ sender: NSButton) {
        // TODO

        window!.sheetParent!.endSheet(window!, returnCode: .OK)
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">ScreenSaver</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">MyScreenSaver</span><span style="color: #000000">: ScreenSaverView {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">frame</span><span style="color: #000000">: NSRect, </span><span style="color: #795E26">isPreview</span><span style="color: #000000">: </span><span style="color: #267F99">Bool</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">frame</span><span style="color: #000000">: frame, </span><span style="color: #795E26">isPreview</span><span style="color: #000000">: isPreview)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">setup</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000">: NSCoder) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000">: coder)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">setup</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">startAnimation</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">animateOneFrame</span><span style="color: #000000">() {  </span><span style="color: #008000">// Optional.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">() { </span><span style="color: #008000">//  Only for the live preview in System Settings.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> hasConfigureSheet: </span><span style="color: #267F99">Bool</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> configureSheetController: ConfigureSheetController?</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> configureSheet: NSWindow? {</span></span>
<span class="line"><span style="color: #000000">        configureSheetController = </span><span style="color: #795E26">ConfigureSheetController</span><span style="color: #000000">(</span><span style="color: #795E26">windowNibName</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;ConfigureSheet&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> configureSheetController?.</span><span style="color: #001080">window</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">ConfigureSheetController</span><span style="color: #000000">: NSWindowController {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> windowNibName: NSNib.Name? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #A31515">&quot;ConfigureSheet&quot;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">windowDidLoad</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">windowDidLoad</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@IBAction</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">okButtonClicked</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">sender</span><span style="color: #000000">: NSButton) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        window!.</span><span style="color: #001080">sheetParent</span><span style="color: #000000">!.</span><span style="color: #795E26">endSheet</span><span style="color: #000000">(window!, </span><span style="color: #795E26">returnCode</span><span style="color: #000000">: .</span><span style="color: #001080">OK</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Providing a preferences sheet</h2>



<p>If you don&#8217;t have any options to configure, you can of course change <code>hasConfigureSheet</code> to return <code>false</code>.  Otherwise, you&#8217;ll need to create a &#8220;ConfigureSheet&#8221; xib file with a window in it containing your screen saver&#8217;s settings.  You can use UserDefaults to save your settings (if you wish), same as any other app.  And you&#8217;ll need to add an &#8220;Okay&#8221; or &#8220;Save&#8221; or similar button to dismiss the sheet.</p>



<h2 class="wp-block-heading">Getting ready to render</h2>



<p>The key methods to implement are <code>setup</code>, with any initial configuration you wish to do (e.g. allocate image or video views, load assets, set up the view hierarchy, etc).</p>



<p><code>ScreenSaverView</code> is an <code>NSView</code> subclass with a flat black background by default, onto which you can add subviews.  Typically for a screen saver you have a very simple view hierarchy &#8211; often just a single view or CoreAnimation layer that you&#8217;re rendering to &#8211; but you can load a xib and insert elements from it into the view if you like.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ It&#8217;s wise to <em>not</em> render anything in <code>setup</code>, which includes accidentally &#8211; you might need to set views to hidden, layers to zero opacity, etc.  This is basically because there can be an arbitrarily long gap between <code>setup</code> and <code>startAnimation</code> calls, and it&#8217;s often weird to render something initially, potentially not animate for a noticeable length of time, and <em>then</em> start actually working properly.</p>



<p>Alternatively, you might insert a placeholder image or text, e.g. &#8220;Loading…&#8221;, if you really want.  But in my opinion it&#8217;s more graceful to just let the initial black screen stand for a moment.</p>
</div></div>



<h2 class="wp-block-heading">Rendering</h2>



<p><code>startAnimation</code> is where you should actually start displaying things.  e.g. if you&#8217;re using an <code>AVPlayer</code>, this is where you actually start it playing (after making it visible, e.g. setting its opacity to 1).</p>



<p><code>animateOneFrame</code> is optional, and is only called if you set the <code>animationTimeInterval</code> property (on <code>self</code>) to a finite, non-zero value in <code>setup</code> (in which case it&#8217;ll be called at intervals <em>at least</em> that long &#8211; it might not be called as often as you desire if previous calls overrun or there&#8217;s other bottlenecks in the screen saver framework).  It&#8217;s essentially just a minor convenience vs having to explicitly set up an <code>NSTimer</code>.</p>



<p>Given how buggy Apple&#8217;s screen saver framework is, I suggest <em>not</em> relying on <code>animateOneFrame</code> if you can at all avoid it.  Even if that means setting up your own timer.  That way when they likely break that too in some future macOS release, your screen saver won&#8217;t necessarily break as well.</p>



<h3 class="wp-block-heading">Bonus topic: fading in</h3>



<p>Unless your screen saver inherently appears gently (e.g. starts rendering with a flat black view and only slowly adds to it), it&#8217;s nice to add a fade-in.  You can do that using CoreAnimation on the view&#8217;s layer:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>override func startAnimation() {
    // Other code…
    
    if let layer {
        layer.opacity = 0.0  // Should already be zero from `setup`, but just to be sure.

        let fadeAnimation = CABasicAnimation(keyPath: "opacity")
        fadeAnimation.fromValue = 0.0
        fadeAnimation.toValue = 1.0
        fadeAnimation.duration = 5  // Seconds.

        // Essential settings to keep the final state
        fadeAnimation.fillMode = .forwards
        fadeAnimation.isRemovedOnCompletion = false

        layer.add(fadeAnimation, forKey: "fadeAnimation")
    }
    
    // Other code…
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> layer {</span></span>
<span class="line"><span style="color: #000000">        layer.</span><span style="color: #001080">opacity</span><span style="color: #000000"> = </span><span style="color: #098658">0.0</span><span style="color: #000000">  </span><span style="color: #008000">// Should already be zero from `setup`, but just to be sure.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> fadeAnimation = </span><span style="color: #795E26">CABasicAnimation</span><span style="color: #000000">(</span><span style="color: #795E26">keyPath</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;opacity&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">fromValue</span><span style="color: #000000"> = </span><span style="color: #098658">0.0</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">toValue</span><span style="color: #000000"> = </span><span style="color: #098658">1.0</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">duration</span><span style="color: #000000"> = </span><span style="color: #098658">5</span><span style="color: #000000">  </span><span style="color: #008000">// Seconds.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Essential settings to keep the final state</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">fillMode</span><span style="color: #000000"> = .</span><span style="color: #001080">forwards</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">isRemovedOnCompletion</span><span style="color: #000000"> = </span><span style="color: #0000FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        layer.</span><span style="color: #795E26">add</span><span style="color: #000000">(fadeAnimation, </span><span style="color: #795E26">forKey</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;fadeAnimation&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Note that you cannot implement a fade-out when the screen saver exits, because macOS hides your screen saver immediately.  Plus, the user might not want a fade-out as they may be in a rush to do something on their computer.</p>



<h3 class="wp-block-heading">Previewing</h3>



<p>You can determine if you&#8217;re running for real or only the preview via the <code>isPreview</code> property (on <code>self</code>).  Many screen savers don&#8217;t care, but particularly if you save any persistent state, you might want to avoid doing that during preview.  For example, in a screen saver which plays a looping video and resumes where it last left off, you probably don&#8217;t want the preview to quietly advance the video.</p>



<h2 class="wp-block-heading">Stopping</h2>



<p><code>stopAnimation</code> is <em>only</em> used for the live preview thumbnail shown in the Screen Saver System Settings pane.  It is <em>never</em> called in normal operation of the screen saver (contrary to what Apple&#8217;s documentation says &#8211; Apple broke that in macOS Sonoma and later).</p>



<p>And that leads to the first path off the official track.  When the screen saver is dismissed by the user, <em>nothing</em> in Apple&#8217;s framework code does anything.  Your view continues to exist, <code>animateOneFrame</code> continues getting called, etc.  Your screen saver just runs in the background, its output not visible, but wasting CPU cycles and RAM.  Worse, if you have sound, that keeps playing.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>🙏 A big thanks to <a href="https://stackoverflow.com/users/4103152/cwizou" data-wpel-link="external" target="_blank" rel="external noopener">cwizou</a> via <a href="https://stackoverflow.com/questions/66861833/audio-keeps-playing-after-screensaver-ends" data-wpel-link="external" target="_blank" rel="external noopener">StackOverflow</a> for documenting <a href="https://stackoverflow.com/a/67161635" data-wpel-link="external" target="_blank" rel="external noopener">the solution</a>, which I&#8217;ve summarised below.</p>
</div></div>



<p>To get around that, you need to register for the <code>com.apple.screensaver.willstop</code> notification, in <code>setup</code>, like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  private func setup() {
    DistributedNotificationCenter.default.addObserver(self,
                                                      selector: #selector(willStop(_:)),
                                                      name: Notification.Name("com.apple.screensaver.willstop"),
                                                      object: nil)
}
    
@objc func willStop(_ notification: Notification) {
    stopAnimation()
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    DistributedNotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">willStop</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">name</span><span style="color: #000000">: Notification.</span><span style="color: #795E26">Name</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;com.apple.screensaver.willstop&quot;</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">willStop</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Note that you still need <code>stopAnimation</code> specifically, because in the live preview in System Settings you won&#8217;t receive that <code>com.apple.screensaver.willstop</code> notification (from the system&#8217;s point of view, the screen saver <em>isn&#8217;t</em> running &#8211; it&#8217;s merely previewing).</p>



<h2 class="wp-block-heading">Handling resumption</h2>



<p>Here&#8217;s the second big bug in Apple&#8217;s screen saver framework &#8211; every time the screen saver starts, your <code>ScreenSaverView</code> subclass is created again. But the old one doesn&#8217;t go anywhere. So now you have <em>two</em> copies running simultaneously, which is at the very least wasteful, and can easily lead to gnarly bugs and weird behaviour (e.g. if both are playing sound, or both modify persistent state).</p>



<p>There are essentially two ways to handle this:</p>



<ol class="wp-block-list">
<li>Kill your own process every time you stop animating.</li>



<li>Manually kill or lame-duck older views when a new one is initialised.</li>
</ol>



<p>Note that you <em>cannot</em> simply check at <code>MyScreenSaver</code> initialisation time if an instance already exists and if so fail initialisation (as is prescribed by <a href="https://zsmb.co/building-a-macos-screen-saver-in-kotlin/#macos-sonoma" data-wpel-link="external" target="_blank" rel="external noopener">this</a> otherwise excellent write-up of this problem), because if you don&#8217;t correctly initialise you&#8217;ll sometimes end up with <em>nothing</em> rendering or running (the screen saver framework appears to not gracefully handle initialisation failures).</p>



<p>Killing your own process can work but has some perils:</p>



<ul class="wp-block-list">
<li>If you kill your process in <code>stopAnimation</code> the screen will flash black momentarily before actually exiting screen saver mode, which is visually annoying.</li>



<li>If the screen saver is restarted rapidly after being interrupted, sometimes you&#8217;ll end up with nothing but a black screen (with no screen saver running).  There&#8217;s evidently some race condition in Apple&#8217;s screen saver system between screen saver processes exiting and being [re]launched.</li>
</ul>



<p>So I recommend not taking that approach.  Instead, you can lame-duck the old view instances.  They&#8217;ll stick around, which is a little wasteful of RAM, but as long as they&#8217;re not rendering or otherwise doing anything, they&#8217;re benign.</p>



<p>There are various ways to implement that, but one of the simpler ones is simply a notification between instances:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>static let NewInstanceNotification = "com.myapp.MyScreenSaver.NewInstance";

var lameDuck = false

private func setup() {
    // Initial setup…
    
    NotificationCenter.default.post(name: MyScreenSaver.NewInstanceNotification, object: self)

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(neuter(_:)),
                                           name: MyScreenSaver.NewInstanceNotification,
                                           object: nil)

    // Further setup…
}

@objc func neuter(_ notification: Notification) {
    lameDuck = true

    stopAnimation()

    self.removeFromSuperview()

    // TODO: any additional cleanup you can, e.g. release image &amp; video files, throw out transient models and state, etc.

    NotificationCenter.default.removeObserver(self)
    DistributedNotificationCenter.default().removeObserver(self)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">static</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> NewInstanceNotification = </span><span style="color: #A31515">&quot;com.myapp.MyScreenSaver.NewInstance&quot;</span><span style="color: #000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> lameDuck = </span><span style="color: #0000FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Initial setup…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">post</span><span style="color: #000000">(</span><span style="color: #795E26">name</span><span style="color: #000000">: MyScreenSaver.</span><span style="color: #001080">NewInstanceNotification</span><span style="color: #000000">, </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">neuter</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">name</span><span style="color: #000000">: MyScreenSaver.</span><span style="color: #001080">NewInstanceNotification</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Further setup…</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">neuter</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    lameDuck = </span><span style="color: #0000FF">true</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">removeFromSuperview</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// TODO: any additional cleanup you can, e.g. release image &amp; video files, throw out transient models and state, etc.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">removeObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    DistributedNotificationCenter.</span><span style="color: #795E26">default</span><span style="color: #000000">().</span><span style="color: #795E26">removeObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>You should check <code>lameDuck</code> at the start of methods like <code>startAnimation</code> or <code>animateOneFrame</code> and exit immediately if it&#8217;s set to <code>true</code>.  Unfortunately, Apple&#8217;s screen saver framework will still call those methods on old instances.</p>



<h2 class="wp-block-heading">Exiting</h2>



<p>Unfortunately Apple&#8217;s screen saver system will never terminate your screen saver process.  Worse, even if you do <em>nothing</em> yourself, Apple&#8217;s screen saver framework code will run in an infinite loop, wasting [a small amount of] CPU time.  So it&#8217;s not great to leave your screen saver process running indefinitely.</p>



<p>Thus, I implement an idle timeout in my screen savers, to have them exit if they&#8217;re not active for a while.  This can be done like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>@MainActor var idleTimeoutWorkItem: DispatchWorkItem? = nil

override func startAnimation() {
    // Other code…
    
    DispatchQueue.main.async {
        if let idleTimeoutWorkItem {
            idleTimeoutWorkItem.cancel()
        }

        idleTimeoutWorkItem = nil
    }
    
    // Other code…
}

override func stopAnimation() {
    // Other code…
    
    if !lameDuck {
        DispatchQueue.main.async {
            idleTimeoutWorkItem?.cancel()

            let workItem = DispatchWorkItem(block: {
                NSApplication.shared.terminate(nil)
            })
            
            idleTimeoutWorkItem = workItem
            
            DispatchQueue.main.asyncAfter(wallDeadline: .now() + 65, execute: workItem)
        }
    }
    
    // Other code…
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> idleTimeoutWorkItem: DispatchWorkItem? = </span><span style="color: #0000FF">nil</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> idleTimeoutWorkItem {</span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem.</span><span style="color: #795E26">cancel</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        idleTimeoutWorkItem = </span><span style="color: #0000FF">nil</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> !lameDuck {</span></span>
<span class="line"><span style="color: #000000">        DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem?.</span><span style="color: #795E26">cancel</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> workItem = </span><span style="color: #795E26">DispatchWorkItem</span><span style="color: #000000">(</span><span style="color: #795E26">block</span><span style="color: #000000">: {</span></span>
<span class="line"><span style="color: #000000">                NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #795E26">terminate</span><span style="color: #000000">(</span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">            })</span></span>
<span class="line"><span style="color: #000000">            </span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem = workItem</span></span>
<span class="line"><span style="color: #000000">            </span></span>
<span class="line"><span style="color: #000000">            DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #795E26">asyncAfter</span><span style="color: #000000">(</span><span style="color: #795E26">wallDeadline</span><span style="color: #000000">: .</span><span style="color: #795E26">now</span><span style="color: #000000">() + </span><span style="color: #098658">65</span><span style="color: #000000">, </span><span style="color: #795E26">execute</span><span style="color: #000000">: workItem)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>I chose the 65 second timeout somewhat arbitrarily.  I figure there&#8217;s a reasonable chance a user will unlock their screen to do something quick, then engage the screen saver again &#8211; all in the less than a minute &#8211; and the cost of idling in the background for an extra minute is small, compared to the cost of relaunching the whole app and reinitialising your renderer.</p>



<p>I added five extra seconds to reduce the probability of aligning with some one-minute timer (e.g. a spurious wake with the screen saver set to start automatically after one minute of no user activity).</p>



<p>You can adjust it however you like.</p>



<h2 class="wp-block-heading">Testing your screen saver</h2>



<p>Double-clicking the built product (your &#8220;.saver&#8221; app) will prompt the user to install it, replacing an old version if necessary.  So that works, though I find it faster to just manually copy the &#8220;.saver&#8221; app to <code>~/Library/Screen Savers</code>.  Just make sure to kill the existing <code>legacyScreenSaver</code> process, if necessary.</p>



<p>You can test it in System Settings, in the Screen Saver pane.  That&#8217;s the only place you can test the live preview part.</p>



<p>But otherwise, I found it easiest to just set one of the screen hot corners to start the screen saver, and use that immediately after copying the new &#8220;.saver&#8221; file into place.</p>



<p>Just be aware that the first time any new copy of the screen saver runs, macOS runs a verification on the bundle, which can take a while if your screen saver is non-trivial in size (e.g. if you bundle large image or video resources).  You&#8217;ll get a black screen with nothing happening, after invoking the screen saver, while that verification is running.</p>



<h2 class="wp-block-heading">Distributing your screen saver</h2>



<p>You don&#8217;t <em>have</em> to sign your screen saver, necessarily, but users will get some annoying error dialogs trying to run it, and will have to fiddle with things in System Settings &#8211; or, if they&#8217;re on a corporate Mac, they might not be able to run it at all.  So it&#8217;s preferable to just sign it.</p>



<p>Xcode doesn&#8217;t support signing screen savers like it does for plain app targets and the like.  So you have to do it manually via the command line, on the built product (your &#8220;.saver&#8221; app).  Thankfully it&#8217;s just two commands (once you have the appropriate stuff set up in your developer account &#8211; note that you will need a paid Apple Developer account, at $99/year).</p>



<p>Follow the instructions <a href="https://www.gabrieluribe.me/blog/how-to-distribute-a-screensaver-on-macos-2022" data-wpel-link="external" target="_blank" rel="external noopener">here</a>, but note that they&#8217;re missing the final step:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>xcrun stapler staple -v MyScreenSaver.saver</p>
</blockquote>



<p>Note that you run it against the screen saver itself, <em>not</em> the zip file.  The zip file&#8217;s just a hack to get Apple&#8217;s notary tool to accept the submission.  It&#8217;s the screen saver bundle itself that&#8217;s actually notarised and signed.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ <code>notarytool</code> uploads your screen saver to Apple.  Be sure it doesn&#8217;t contain anything you&#8217;re not happy being potentially public (Apple will <em>presumably</em> try to keep your uploads private to Apple, and <em>might</em> not intentionally store them forever, but I wouldn&#8217;t bet my life on their confidentiality).</p>
</div></div>



<h2 class="wp-block-heading">Conclusion</h2>



<p>That&#8217;s &#8220;it&#8221; in a superficial sense &#8211; if you&#8217;ve followed all this so far, you have a <em>roughly</em> working screen saver.</p>



<p>But there are a lot more bugs and nuances thereof that may afflict you, depending on what you&#8217;re doing in your screen saver.  So good luck. 😣</p>



<h2 class="wp-block-heading">Addendum: SaveHollywood</h2>



<p>I found that looking at existing open source screen savers was <em>partially</em> helpful, but also sometimes misleading.  e.g. for my most recent screen saver I basically just play a video in a loop (which <em>should</em> be embarrassingly trivial but took two weeks to get working properly, thanks to the aforementioned bugs in Apple&#8217;s frameworks, among many others).  In that case I looked at <a href="https://github.com/packagesdev/savehollywood" data-wpel-link="external" target="_blank" rel="external noopener">SaveHollywood</a>, a similar screen saver, for aid and ideas.</p>



<p>Unfortunately, SaveHollywood is abandoned and doesn&#8217;t work on recent versions of macOS.  The way it does some things is archaic and either not the best way or not functional at all.</p>



<p>Nonetheless, it did help with some of the higher-level aspects, above the screen saver machinery itself, like how to use <code>AVPlayer</code> in a screen saver.</p>



<p>So, <em>do</em> check out similar, existing screen savers (and of course just use them directly if they suit your needs!) but beware of obsolete or otherwise incorrect code.</p>



<h2 class="wp-block-heading">Addendum:  What this says about Apple</h2>



<p>What really troubles me about the screen saver system is what it says about Apple&#8217;s approach to the Mac, software, and their users.  Which is sadly just the same thing we&#8217;ve been seeing for years now.</p>



<p>Screen savers used to work fine.  There was an API established <em>long</em> ago that was lightweight, straight-forward, and effective.  All Apple had to do was <em>not break it</em>.</p>



<p>And <em>how</em> they broke it is troubling.  Perhaps it was prompted by some otherwise unrelated but well-meaning refactor &#8211; pulling screen saver code out into a separate, sandboxed process, perhaps, for improved system security.  Fine.  But if it&#8217;s worth changing it&#8217;s worth changing <em>properly</em>.  It&#8217;s very clear that whomever did the changes either (a) didn&#8217;t care that they broke things badly, or (b) didn&#8217;t care to check.</p>



<p>It&#8217;s that recurring theme of <em>not caring</em> that&#8217;s most disappointing in today&#8217;s Apple.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2025/12/screen-saver-system-settings-panel-with-my-screen-saver-selected.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8580</post-id>	</item>
		<item>
		<title>presentedWindowStyle is not windowStyle</title>
		<link>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/</link>
					<comments>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 23 Sep 2024 23:12:29 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Feedback Assistant]]></category>
		<category><![CDATA[Happy]]></category>
		<category><![CDATA[presentedWindowStyle]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[windowStyle]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8460</guid>

					<description><![CDATA[This post is mostly to herald a pretty good Apple bug report response, which as we know is a too-rare event. But it might also help others with this confusing SwiftUI API. What&#8217;s the difference between presentedWindowStyle(_:) and windowStyle(_:)? Well, one does something, the other doesn&#8217;t, basically. I tried using the former, and observed that&#8230; <a class="read-more-link" href="https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This post is mostly to herald a pretty good Apple bug report response, which as we know is a too-rare event.  But it might also help others with this confusing SwiftUI API.</p>



<p>What&#8217;s the difference between <code><a href="https://developer.apple.com/documentation/swiftui/view/presentedwindowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener">presentedWindowStyle(_:)</a></code> and <code><a href="https://developer.apple.com/documentation/swiftui/scene/windowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener">windowStyle(_:)</a></code>?</p>



<p>Well, one does something, the other doesn&#8217;t, basically.</p>



<p>I tried using the former, and observed that it never has any effect.  I filed FB14892608 about it, a month ago.  While the long delay isn&#8217;t great, the response I got today was actually pretty helpful:</p>



<pre class="wp-block-preformatted">We’re sorry you ran into trouble with that API.<br><br>To adjust the style of a window group’s windows, you have a couple of options. If all of the windows in the group should have the same style, then using the windowStyle() scene modifier is what you want:<br><br>struct MyApp: App {<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>        }<br>        .windowStyle(.hiddenTitleBar)<br>    }<br>}<br><br>If you need to adjust the style on an individual basis using state for that window, there are also some related view modifiers:<br>struct MyApp: App {<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>                .toolbar(removing: .title)<br>                .toolbarBackgroundVisibility(.hidden, for: .windowToolbar)<br>        }<br>    }<br>}</pre>



<p>A simple apology followed by clear and specific instructions on how to actually achieve what I wanted.  I genuinely applaud Apple &#8211; or perhaps I should more specifically thank the individual(s) within DTS that actually provided this response.  Either way, it was a very pleasant surprise.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<p>Now, it still leaves unanswered the question of what <code>presentedWindowStyle(_:)</code> is supposed to be for, and in what situations it actually <em>does</em> anything.  So not a perfect reply, per se, but really the proper solution for that is either:</p>



<ol style="list-style-type:upper-alpha" class="wp-block-list">
<li>Fix the API to not have such confusingly similar modifiers.</li>



<li>Improve the documentation to:
<ul class="wp-block-list">
<li>Explicitly point out the other, similarly-named modifier.</li>



<li>Distinguish them.</li>
</ul>
</li>
</ol>



<p>Currently the documentation is just:</p>



<figure class="wp-block-pullquote"><blockquote><p>Sets the style for windows created by this scene.</p><cite><a href="https://developer.apple.com/documentation/swiftui/scene/windowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener"><code>windowStyle(_:)</code> documentation</a></cite></blockquote></figure>



<p>Versus:</p>



<figure class="wp-block-pullquote"><blockquote><p>Sets the style for windows created by interacting with this view.</p><cite><a href="https://developer.apple.com/documentation/swiftui/view/presentedwindowstyle(_:)" data-wpel-link="external" target="_blank" rel="external noopener"><code>presentedWindowStyle(_:)</code> documentation</a></cite></blockquote></figure>



<p>The latter might <em>technically</em> convey something important here, in the <em>created by interacting with this view</em> part, but so much in SwiftUI is modifiers stuck haphazardly in weird and arbitrary places, that I&#8217;m been conditioned to ignore the fact that I&#8217;m often applying modifiers to views that don&#8217;t actually affect those views.  And for all I know in SwiftUI parlance views <em>do</em> &#8220;create&#8221; and/or &#8220;interact&#8221; with their parent windows (a <em>lot</em> about SwiftUI is backwards, part of its nature as a declarative API).</p>



<p>Especially if that&#8217;s the first candidate API you stumble across, when searching for a way to style a window, it&#8217;s very easy to presume it&#8217;s the right API.  Why would there be <em>multiple</em> APIs for styling the window; why would you continue searching after finding one?</p>



<p>Similarly, the <code>presentedWindowStyle(_:)</code> variant is the <em>only</em> one that appears in Xcode&#8217;s auto-complete if you try to add the modifier to your main view, which is both where you sometimes <em>have</em> to add window-level modifiers and also just a really easy mistake to make (instead of adding the modifier to the <code><a href="https://developer.apple.com/documentation/swiftui/windowgroup" data-wpel-link="external" target="_blank" rel="external noopener">WindowGroup</a></code>, one indentation level up).</p>



<p>Lastly, it doesn&#8217;t help that I can&#8217;t find <em>any</em> situation in which <code>presentedWindowStyle(_:)</code> has any effect, even knowing it&#8217;s not intended to style the parent window. One might assume it&#8217;s somehow related to sheets or somesuch, but apparently not? Presumably I&#8217;m overlooking some use-case &#8211; or maybe it <em>doesn&#8217;t</em> actually do anything on macOS, only iDevices? I welcome clues or tips.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8460</post-id>	</item>
		<item>
		<title>&#8220;Import from iPhone or iPad&#8221; doesn&#8217;t work when any view contains a SwiftUI Toggle</title>
		<link>https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/</link>
					<comments>https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 23:22:42 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Continuity Camera]]></category>
		<category><![CDATA[Import from iPhone or iPad]]></category>
		<category><![CDATA[importableFromServices]]></category>
		<category><![CDATA[ImportFromDevicesCommands]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Toggle]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8371</guid>

					<description><![CDATA[This is a public reposting of FB14893699, in case it’s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it. If any view in the [active] window contains a Toggle &#8211; even one that’s disabled or hidden &#8211; then Continuity Camera (re. ImportFromDevicesCommands and&#8230; <a class="read-more-link" href="https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>This is a public reposting of FB14893699, in case it’s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it.</p>
</div></div>



<p>If <em>any</em> view in the [active] window contains a <a href="https://developer.apple.com/documentation/swiftui/toggle" data-wpel-link="external" target="_blank" rel="external noopener"><code>Toggle</code></a> &#8211; even one that’s disabled or hidden &#8211; then Continuity Camera (re. <code><a href="http://ImportFromDevicesCommands" data-wpel-link="external" target="_blank" rel="external noopener">ImportFromDevicesCommands</a></code> and <code><a href="https://developer.apple.com/documentation/scenekit/sceneview/4049460-importablefromservices" data-wpel-link="external" target="_blank" rel="external noopener">importableFromServices</a></code>) doesn’t work; all the submenu items under “Import from iPhone or iPad” are disabled.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img fetchpriority="high" decoding="async" width="412" height="309" src="https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled.webp" alt="Screenshot of the File menu with the &quot;Import from iPhone or iPad&quot; submenu expanded, and all items therein are disabled." class="wp-image-8372" srcset="https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled.webp 412w, https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled-256x192.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled@2x.webp 824w" sizes="(max-width: 412px) 100vw, 412px" /></figure>
</div>


<p>I don’t know if this is truly specific to <code>Toggle</code>, that’s just the example case I happen to have isolated [first?].</p>



<p>What’s really weird is that once a <code>Toggle</code> has <em>ever</em> been displayed, even if you subsequently remove it from the view hierarchy entirely the “Import from iPhone or iPad” submenu items all remain disabled.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@main</span></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Example</span><span style="color: #000000">: App {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@State</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> breakImportFromiDevice = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@State</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> text = </span><span style="color: #A31515">&quot;&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some Scene {</span></span>
<span class="line"><span style="color: #000000">        WindowGroup {</span></span>
<span class="line"><span style="color: #000000">            VStack {</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #795E26">TextField</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Input&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">text</span><span style="color: #000000">: $text) </span><span style="color: #008000">// Doesn&#39;t break anything.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">if</span><span style="color: #000000"> breakImportFromiDevice {</span></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #795E26">Toggle</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Break import from iDevice&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">isOn</span><span style="color: #000000">: $breakImportFromiDevice)</span></span>
<span class="line"><span style="color: #000000">                }</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"><span style="color: #000000">            .</span><span style="color: #795E26">importableFromServices</span><span style="color: #000000">(</span><span style="color: #795E26">action</span><span style="color: #000000">: { (</span><span style="color: #795E26">images</span><span style="color: #000000">: [NSImage]) -&gt; </span><span style="color: #267F99">Bool</span><span style="color: #000000"> </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Load image!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">            })</span></span>
<span class="line"><span style="color: #000000">        }.</span><span style="color: #001080">commands</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">ImportFromDevicesCommands</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span></code></pre></div>



<p>There are some circumstances in which this gets “unbroken” during interactions with other views and so forth, which is 100% reproducible in my real app but I have no idea what the reason is.  The steps involved in my real app are kinda ridiculous (and not in any way remotely a viable workaround) and make absolutely no sense &#8211; it only “unbreaks” when a specific view is in a specific weird state (itself kind of the result of a bug, albeit a benign one).  And that weird state is merely whether it’s displaying one image or another &#8211; which as far as SwiftUI is concerned is not even a difference in view state, since I’m just swapping <code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code>s under the cover.</p>



<p>I figured it must be something to do with view focus, but after much experimentation I believe I can conclusively rule that out.  No matter which view has focus, or how focus is configured, or which views are even focusable at all, the problem persists.  Likewise for window focus and key state.  And, [accessability-]focusable views other than <code>Toggle</code> &#8211; e.g. <code><a href="https://developer.apple.com/documentation/swiftui/textfield" data-wpel-link="external" target="_blank" rel="external noopener">TextField</a></code> &#8211; don’t cause any issues.</p>



<p>Frankly it&#8217;s baffling, and a mite infuriating.  I can&#8217;t even conceive of how SwiftUI can be so incredibly broken regarding such basic functionality, and the apparent interaction of GUI elements that have absolutely no business together.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<p>Tangentially, a few things I&#8217;ve noticed about this Continuity Camera feature:</p>



<ul class="wp-block-list">
<li>&#8220;Add Sketch&#8221; doesn&#8217;t do anything.  Unlike the other options, which open the camera app on the target iDevice, it has no effect. 🤷‍♂️</li>



<li>Within the Finder, you can right-click empty whitespace within a folder, and the contextual menu has this &#8220;Import from iPhone or iPad&#8221; option at the bottom.  That&#8217;s pretty handy &#8211; until now I&#8217;d been doing it the &#8220;hard&#8221; way by taking a photo on my iPhone and AirDropping it across to my Mac.<br><br>I&#8217;d never noticed that feature prior to debugging this problem (I wanted to confirm that multiple other apps worked just fine; that it wasn&#8217;t an issue with Continuity Camera system-wide).</li>



<li>TextEdit has the same feature but tweaks the wording to &#8220;Insert…&#8221; rather than &#8220;Import…&#8221;, which I thought is both a nice touch and frustratingly not something you can do in your own apps (at least, not in SwiftUI). 😕</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/import-from-iphone-or-ipad-doesnt-work-when-any-view-contains-a-swiftui-toggle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/08/22import-from-iphone-or-ipad22-disabled.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8371</post-id>	</item>
		<item>
		<title>NSPasteboard crashes due to unsafe, internal concurrent memory mutation when handling file promises</title>
		<link>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/</link>
					<comments>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 05:01:02 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Drag & drop]]></category>
		<category><![CDATA[memory corruption]]></category>
		<category><![CDATA[NSItemProvider]]></category>
		<category><![CDATA[NSPasteboard]]></category>
		<category><![CDATA[NSPasteboardItem]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8369</guid>

					<description><![CDATA[This is a public reposting of FB14885505, in case it&#8217;s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it. NSPasteboard mutates itself simultaneously from the main thread and the global concurrent Dispatch pool, w.r.t. to its internal type cache. This is surprisingly trivial&#8230; <a class="read-more-link" href="https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>This is a public reposting of FB14885505, in case it&#8217;s helpful to anyone else or especially in case someone else has seen this too and knows how to work around it.</p>
</div></div>



<p><code><a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a></code> mutates itself simultaneously from the main thread and the global concurrent <a href="https://developer.apple.com/documentation/DISPATCH" data-wpel-link="external" target="_blank" rel="external noopener">Dispatch</a> pool, w.r.t. to its internal type cache. This is surprisingly trivial to reproduce (sample code below) by just dropping, e.g. a file promise (such as by opening a PNG in Preview, revealing the thumbnails sidebar, and then dragging the thumbnail onto the sample project’s window).</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">ContentView</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">Rectangle</span><span style="color: #000000">().</span><span style="color: #795E26">onDrop</span><span style="color: #000000">(</span><span style="color: #795E26">of</span><span style="color: #000000">: NSImage.</span><span style="color: #001080">imageTypes</span><span style="color: #000000">, </span><span style="color: #795E26">isTargeted</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">) { </span><span style="color: #001080">_</span><span style="color: #000000"> </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> pb = </span><span style="color: #795E26">NSPasteboard</span><span style="color: #000000">(</span><span style="color: #795E26">name</span><span style="color: #000000">: .</span><span style="color: #001080">drag</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #001080">_</span><span style="color: #000000"> = pb.</span><span style="color: #001080">pasteboardItems</span><span style="color: #000000"> </span><span style="color: #008000">// Seems to be necessary for the crash.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #001080">_</span><span style="color: #000000"> = NSImage.</span><span style="color: #001080">imageTypes</span><span style="color: #000000"> </span><span style="color: #008000">// Not strictly necessary for the crash, but seems to make it more likely. 🤷‍♂️</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Judging from the callstack that runs in the concurrent pool, this is specific to file promises (and that seems to match my experience &#8211; it only crashes for some test cases, all of which involve file promises being present in the drag pasteboard at the time of the drop).</p>



<p>Since this bug causes semi-random memory corruption, it manifests in a large number of ways &#8211; not all of which are all that helpful.  But at least one case I’ve seen a few times is helpful, as it clearly shows the offending internal <code>NSPasteboard</code> code running concurrently with itself, e.g.:</p>



<pre class="wp-block-preformatted">Main queue / thread:
#0	0x00007ff80108dab5 in _platform_bzero$VARIANT$Haswell ()
#1	0x000000010df8774d in GuardMalloc_mallocInternal ()
#2	0x00007ff90792542f in stack_logging_lite_malloc ()
#3	0x00007ff800ea8733 in _malloc_zone_malloc_instrumented_or_legacy ()
#4	0x00007ff800f39a72 in _vasprintf ()
#5	0x00007ff800f16922 in asprintf ()
#6	0x00007ff80125655a in -[NSObject(NSObject) __dealloc_zombie] ()
#7	0x00007ff8020e039a in -[NSConcreteMapTable dealloc] ()
#8	0x00007ff804876983 in -[NSPasteboard _updateTypeCacheIfNeeded] ()
#9	0x00007ff8048763df in -[NSPasteboard _typesAtIndex:combinesItems:] ()
#10	0x00007ff804aad148 in NSCoreDragReceiveMessageProc ()
#11	0x00007ff807517b1a in CallReceiveMessageCollectionWithMessage ()
#12	0x00007ff8075124fa in DoMultipartDropMessage ()
#13	0x00007ff8075122ce in DoDropMessage ()
#14	0x00007ff8075159a9 in CoreDragMessageHandler ()
#15	0x00007ff8011d776b in __CFMessagePortPerform ()
#16	0x00007ff80113e5b7 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
#17	0x00007ff80113e4ee in __CFRunLoopDoSource1 ()
#18	0x00007ff80113d166 in __CFRunLoopRun ()
#19	0x00007ff80113c112 in CFRunLoopRunSpecific ()
#20	0x00007ff80bb55a09 in RunCurrentEventLoopInMode ()
#21	0x00007ff80bb55646 in ReceiveNextEventCommon ()
#22	0x00007ff80bb55561 in _BlockUntilNextEventMatchingListInModeWithFilter ()
#23	0x00007ff8047acc61 in _DPSNextEvent ()
#24	0x00007ff8050c0dc0 in -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] ()
#25	0x00007ff80479e075 in -[NSApplication run] ()
#26	0x00007ff804771ff3 in NSApplicationMain ()
#27	0x00007ff90dc24557 in ___lldb_unnamed_symbol57096 ()
#28	0x00007ff90e31fe64 in ___lldb_unnamed_symbol104448 ()
#29	0x00007ff90e6e63ff in static SwiftUI.App.main() -&gt; () ()
#30	0x000000010dfa5cce in static NSPasteboardItem_CrashApp.$main() ()
#31	0x000000010dfa5d69 in main at /Users/SadPanda/Documents/NSPasteboardItem Crash/NSPasteboardItem Crash/NSPasteboardItem_CrashApp.swift:11
#32	0x00007ff800cd5366 in start ()

Dispatch concurrent queue (default QoS):
#0	0x00007ff80111b45c in -[__NSSetM addObject:] ()
#1	0x00007ff80487692e in -[NSPasteboard _updateTypeCacheIfNeeded] ()
#2	0x00007ff8048763df in -[NSPasteboard _typesAtIndex:combinesItems:] ()
#3	0x00007ff804aa9597 in -[NSPasteboard _canRequestDataForType:index:usesPboardTypes:combinesItems:] ()
#4	0x00007ff804fdd161 in -[NSPasteboard _dataForType:index:usesPboardTypes:combinesItems:securityScoped:] ()
#5	0x00007ff804aa7c4b in -[NSPasteboardItem dataForType:] ()
#6	0x00007ff8055804af in -[NSFilePromiseReceiver receivePromisedFilesAtDestination:options:operationQueue:reader:] ()
#7	0x00007ff90e787b51 in ___lldb_unnamed_symbol131674 ()
#8	0x00007ff90e9bc340 in ___lldb_unnamed_symbol148147 ()
#9	0x00007ff8020d00ba in __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ ()
#10	0x00007ff8020cffb8 in -[NSBlockOperation main] ()
#11	0x00007ff8020cff4b in __NSOPERATION_IS_INVOKING_MAIN__ ()
#12	0x00007ff8020cf1ec in -[NSOperation start] ()
#13	0x00007ff8020cef0d in __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ ()
#14	0x00007ff8020cedde in __NSOQSchedule_f ()
#15	0x000000010e68ce7d in _dispatch_block_async_invoke2 ()
#16	0x000000010e67ca7b in _dispatch_client_callout ()
#17	0x000000010e67fa09 in _dispatch_continuation_pop ()
#18	0x000000010e67eae8 in _dispatch_async_redirect_invoke ()
#19	0x000000010e6906a9 in _dispatch_root_queue_drain ()
#20	0x000000010e6911ba in _dispatch_worker_thread2 ()
#21	0x000000010dfb832f in _pthread_wqthread ()
#22	0x000000010dfbebeb in start_wqthread ()</pre>



<p>There doesn’t appear to be any workaround (short of not supporting drops at all!).</p>



<p>The more complicated the drop handler the more likely it is to promptly crash upon drop &#8211; in my real code with a non-trivial handler, it’s virtually guaranteed to crash on the second drop containing a file promise, while in the vastly reduced sample code (above) it can take dozens of drops before it finally crashes outright.</p>



<p>I have not <em>directly</em> tested whether this <code>NSPasteboard</code> bug occurs in the absence of <a href="https://developer.apple.com/documentation/SwiftUI" data-wpel-link="external" target="_blank" rel="external noopener">SwiftUI</a>, so I don’t strictly know if the root cause is in <a href="https://developer.apple.com/documentation/appkit" data-wpel-link="external" target="_blank" rel="external noopener">AppKit</a> or SwiftUI. However, since most SwiftUI apps don’t support drag-and-drop, but plenty of AppKit ones do and manage to not crash when given the exact same test cases, I do strongly suspect SwiftUI is causing this somehow.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ You may wonder why I’m directly accessing the drag pasteboard rather than using the <code><a href="https://developer.apple.com/documentation/foundation/nsitemprovider" data-wpel-link="external" target="_blank" rel="external noopener">NSItemProvider</a></code>s provided by SwiftUI. It’s because that API is horribly broken &#8211; in many cases the provided <code>NSItemProvider</code>(s) are duds that contain no actual data. So I have to use the drag pasteboard directly in order to stand any chance of supporting drag &amp; drop.</p>



<p>Also, the <code>NSItemProvider</code>-based API is harder to use and doesn&#8217;t support important aspects of drag-and-drop, like file promises (although, with <code>NSPasteboard</code> apparently corrupting itself when file promises are received, I guess none of Apple&#8217;s APIs do anymore 😔).</p>
</div></div>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h3 class="wp-block-heading">Follow-up (September 12th, 2024)</h3>



<p>I actually received a response from Apple, from a real human (or at least a convincing AI).  Ultimately their response didn&#8217;t help as it contained some mistakes, but I&#8217;m hopeful there&#8217;ll be more follow-up and a productive conclusion.  And in the interim, they did assert a few things which are important to know, and are not otherwise documented by Apple:</p>



<ul class="wp-block-list">
<li>SwiftUI&#8217;s <code><a href="https://developer.apple.com/documentation/swiftui/view/ondrop(of:istargeted:perform:)-f15m" data-wpel-link="external" target="_blank" rel="external noopener">onDrop(of:isTargeted:perform:)</a></code> method makes no claims or promises as to what thread / queue it executes the closure on, and in fact according to the anonymous Apple engineer it <em>never</em> executes the closure on the main thread.<br><br>Now, while that may be the intent, the reality of that is wrong &#8211; in my experience it <em>always</em> executes the closure on the main thread (which makes a lot of sense to me as drag-and-drop event handling in AppKit has always been on the main thread in practice).<br><br>Nonetheless, Apple says one cannot rely on the current behaviour and should in fact assume it <em>never</em> executes on the main thread (though in practice that means you have to <em>check</em>, not assume, since if you blindly do something like <code>DispatchQueue.main.sync</code><code style="font-size: 15px;"> { … }</code> in your drop handler your code <em>will</em> deadlock, today).</li>



<li><code><a href="https://developer.apple.com/documentation/appkit/nspasteboard" data-wpel-link="external" target="_blank" rel="external noopener">NSPasteboard</a></code> is not safe to use outside the main thread / queue.<br><br>This isn&#8217;t documented anywhere public &#8211; not in <code>NSPasteboard</code>&#8216;s documentation itself, nor the ancient <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">Application Kit Framework Thread Safety</a> documentation.<br><br>I wouldn&#8217;t be surprised if it&#8217;s broadly true, as the AppKit APIs involving it always seemed main-thread centric anyway (all the handlers and delegate methods involving pasteboards are invoked on the main thread, in my experience).  And it&#8217;s generally best to assume everything in AppKit is main-thread-only unless it&#8217;s explicitly documented otherwise.<br><br>However, it&#8217;s important to note that Apple&#8217;s <em>own</em> code doesn&#8217;t follow this rule &#8211; e.g. <code><a href="https://developer.apple.com/documentation/appkit/nsfilepromisereceiver" data-wpel-link="external" target="_blank" rel="external noopener">NSFilePromiseReceiver</a></code>, internally, uses <code>NSPasteboard</code> from the global concurrent queue.</li>
</ul>



<p>Even though Apple&#8217;s initial response to this bug report hasn&#8217;t been all that fruitful, I do want to emphasise the fact that they <em>did</em> respond, which was a pleasant surprise and very much appreciated.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8369</post-id>	</item>
		<item>
		<title>Calling Swift Concurrency async code synchronously in Swift</title>
		<link>https://wadetregaskis.com/calling-swift-concurrency-async-code-synchronously-in-swift/</link>
					<comments>https://wadetregaskis.com/calling-swift-concurrency-async-code-synchronously-in-swift/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 21 Aug 2024 00:09:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[spherical chicken in a vacuum]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Swift Concurrency]]></category>
		<category><![CDATA[Task]]></category>
		<category><![CDATA[withoutActuallyEscaping]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8351</guid>

					<description><![CDATA[Sometimes you just need to shove a round peg into a square hole. Sometimes that genuinely is the best option (or perhaps more accurately: the least bad option). I find my hand is often forced by APIs I don&#8217;t control (most often Apple&#8217;s APIs). e.g. data source or delegate callbacks that are synchronous and require&#8230; <a class="read-more-link" href="https://wadetregaskis.com/calling-swift-concurrency-async-code-synchronously-in-swift/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Sometimes you just need to shove a round peg into a square hole.  Sometimes that genuinely is the best option (or perhaps more accurately: the least bad option).</p>



<p>I find my hand is often forced by APIs I don&#8217;t control (most often Apple&#8217;s APIs).  e.g. data source or delegate callbacks that are synchronous<sup data-fn="fca8ab2f-af18-4e50-8bde-9aa3e28a1927" class="fn"><a href="#fca8ab2f-af18-4e50-8bde-9aa3e28a1927" id="fca8ab2f-af18-4e50-8bde-9aa3e28a1927-link">1</a></sup> and require you to return a value, but in order to obtain that value you have to run async code (perhaps because yet again that&#8217;s all you&#8217;re given by 3rd parties, or because that code makes sense to be async and is used happily as such in other places and you don&#8217;t want to have to duplicate it in perpetuity just to have a sync version).</p>



<p>If that asynchronosity is achieved through e.g. <a href="https://developer.apple.com/documentation/DISPATCH" data-wpel-link="external" target="_blank" rel="external noopener">GCD</a> or <a href="https://developer.apple.com/documentation/foundation/nsrunloop" data-wpel-link="external" target="_blank" rel="external noopener">NSRunLoop</a> or <a href="https://developer.apple.com/documentation/foundation/process" data-wpel-link="external" target="_blank" rel="external noopener">NSProcess</a> or <a href="https://developer.apple.com/documentation/foundation/nstask" data-wpel-link="external" target="_blank" rel="external noopener">NSTask</a> or <a href="https://developer.apple.com/documentation/foundation/nsthread" data-wpel-link="external" target="_blank" rel="external noopener">NSThread</a> or <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/pthread.3.html" data-wpel-link="external" target="_blank" rel="external noopener">pthreads</a>, it&#8217;s easy.  There are numerous ways to synchronously wait on their tasks.  In contrast, <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/" data-wpel-link="external" target="_blank" rel="external noopener">Swift Concurrency</a> <em>really</em> doesn&#8217;t want you to do this.  The language and standard library take an adamant idealogical position on this &#8211; one which is unfortunately impractical; a <a href="https://en.wikipedia.org/wiki/Spherical_cow" data-wpel-link="external" target="_blank" rel="external noopener">spherical chicken in a vacuum</a><sup data-fn="3f7186da-728e-41f5-b12b-6b7ca7456625" class="fn"><a href="#3f7186da-728e-41f5-b12b-6b7ca7456625" id="3f7186da-728e-41f5-b12b-6b7ca7456625-link">2</a></sup>.</p>



<p>Nonetheless, despite Swift&#8217;s best efforts to prevent me, I believe I&#8217;ve come up with a way to do this.  It appears to work reliably, given fairly extensive testing.  Nonetheless, I do not make any promises.  Use at your own risk.</p>



<p>If you know of a better way, please do let me know (e.g. in the comments below).</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="import Dispatch

extension Task {
    /// Executes the given async closure synchronously, waiting for it to finish before returning.
    ///
    /// **Warning**: Do not call this from a thread used by Swift Concurrency (e.g. an actor, including global actors like MainActor) if the closure - or anything it calls transitively via `await` - might be bound to that same isolation context.  Doing so may result in deadlock.
    static func sync(_ code: sending () async throws(Failure) -&gt; Success) throws(Failure) -&gt; Success { // 1
        let semaphore = DispatchSemaphore(value: 0)

        nonisolated(unsafe) var result: Result&lt;Success, Failure&gt;? = nil // 2

        withoutActuallyEscaping(code) { // 3
            nonisolated(unsafe) let sendableCode = $0 // 4

            let coreTask = Task&lt;Void, Never&gt;.detached(priority: .userInitiated) { @Sendable () async -&gt; Void in // 5
                do {
                    result = .success(try await sendableCode())
                } catch {
                    result = .failure(error as! Failure)
                }
            }

            Task&lt;Void, Never&gt;.detached(priority: .userInitiated) { // 6
                await coreTask.value
                semaphore.signal()
            }

            semaphore.wait()
        }

        return try result!.get() // 7
    }
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">Dispatch</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">Task</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">/// Executes the given async closure synchronously, waiting for it to finish before returning.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">///</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">/// **Warning**: Do not call this from a thread used by Swift Concurrency (e.g. an actor, including global actors like MainActor) if the closure - or anything it calls transitively via `await` - might be bound to that same isolation context.  Doing so may result in deadlock.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">static</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">sync</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">code</span><span style="color: #000000">: sending () </span><span style="color: #AF00DB">async</span><span style="color: #000000"> </span><span style="color: #AF00DB">throws</span><span style="color: #000000">(Failure) -&gt; Success) </span><span style="color: #AF00DB">throws</span><span style="color: #000000">(Failure) -&gt; Success { </span><span style="color: #008000">// 1</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> semaphore = </span><span style="color: #795E26">DispatchSemaphore</span><span style="color: #000000">(</span><span style="color: #795E26">value</span><span style="color: #000000">: </span><span style="color: #098658">0</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">nonisolated</span><span style="color: #000000">(unsafe) </span><span style="color: #0000FF">var</span><span style="color: #000000"> result: Result&lt;Success, Failure&gt;? = </span><span style="color: #0000FF">nil</span><span style="color: #000000"> </span><span style="color: #008000">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">withoutActuallyEscaping</span><span style="color: #000000">(code) { </span><span style="color: #008000">// 3</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">nonisolated</span><span style="color: #000000">(unsafe) </span><span style="color: #0000FF">let</span><span style="color: #000000"> sendableCode = </span><span style="color: #0000FF">$0</span><span style="color: #000000"> </span><span style="color: #008000">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> coreTask = Task&lt;</span><span style="color: #267F99">Void</span><span style="color: #000000">, Never&gt;.</span><span style="color: #795E26">detached</span><span style="color: #000000">(</span><span style="color: #795E26">priority</span><span style="color: #000000">: .</span><span style="color: #001080">userInitiated</span><span style="color: #000000">) { </span><span style="color: #0000FF">@Sendable</span><span style="color: #000000"> () async -&gt; </span><span style="color: #267F99">Void</span><span style="color: #000000"> </span><span style="color: #AF00DB">in</span><span style="color: #000000"> </span><span style="color: #008000">// 5</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">                    result = .</span><span style="color: #795E26">success</span><span style="color: #000000">(</span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">sendableCode</span><span style="color: #000000">())</span></span>
<span class="line"><span style="color: #000000">                } </span><span style="color: #AF00DB">catch</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">                    result = .</span><span style="color: #795E26">failure</span><span style="color: #000000">(error as! Failure)</span></span>
<span class="line"><span style="color: #000000">                }</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            Task&lt;</span><span style="color: #267F99">Void</span><span style="color: #000000">, Never&gt;.</span><span style="color: #795E26">detached</span><span style="color: #000000">(</span><span style="color: #795E26">priority</span><span style="color: #000000">: .</span><span style="color: #001080">userInitiated</span><span style="color: #000000">) { </span><span style="color: #008000">// 6</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">await</span><span style="color: #000000"> coreTask.</span><span style="color: #001080">value</span></span>
<span class="line"><span style="color: #000000">                semaphore.</span><span style="color: #795E26">signal</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            semaphore.</span><span style="color: #795E26">wait</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #AF00DB">try</span><span style="color: #000000"> result!.</span><span style="color: #795E26">get</span><span style="color: #000000">() </span><span style="color: #008000">// 7</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Elaborating on some of the odder or less than self-explanatory aspects of this:</p>



<ol class="wp-block-list">
<li>The closure parameter <em>must</em> be <code><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0430-transferring-parameters-and-results.md" data-wpel-link="external" target="_blank" rel="external noopener">sending</a></code> otherwise this deadlocks if e.g. you call it from the main thread (even if the closure, and all its transitive async calls, are not isolated to the main thread).  I don&#8217;t understand why this happens &#8211; it&#8217;s <em>possibly</em> explicable and working as intended, but <a href="https://github.com/swiftlang/swift/issues/75866" data-wpel-link="external" target="_blank" rel="external noopener">I wonder if it&#8217;s simply a bug</a>.  Nobody has been able to explain why it happens.<br><br>Note: in the initial version of this post I accidentally omitted this essential keyword.  I apologise for the error, and hope it didn&#8217;t cause grief for anyone.</li>



<li>Since there&#8217;s no sync way to retrieve the result of a <code><a href="https://developer.apple.com/documentation/swift/task" data-wpel-link="external" target="_blank" rel="external noopener">Task</a></code>, the result has to be passed out through a side-channel. The <code><a href="https://www.swift.org/blog/swift-5.10-released/#unsafe-opt-outs" data-wpel-link="external" target="_blank" rel="external noopener">nonisolated(unsafe)</a></code> is to silence the Swift 6 compiler&#8217;s erroneous error diagnostics about concurrent mutation of shared state.</li>



<li><code>Task</code> constructors only accept <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Escaping-Closures" data-wpel-link="external" target="_blank" rel="external noopener">escaping closures</a>, even though as they&#8217;re used here the closure never <em>actually</em> escapes. Fortunately the <code><a href="https://developer.apple.com/documentation/swift/withoutactuallyescaping(_:do:)" data-wpel-link="external" target="_blank" rel="external noopener">withoutActuallyEscaping</a></code> escape hatch is available.</li>



<li><code>code</code> isn&#8217;t <code><a href="https://developer.apple.com/documentation/swift/sendable#Sendable-Functions-and-Closures" data-wpel-link="external" target="_blank" rel="external noopener">@Sendable</a></code> &#8211; since it doesn&#8217;t <em>actually</em> have to be sent in the sense of executing concurrently &#8211; so trying to use it in the <code>Task</code> closure below, which is <code>@Sendable</code>, results in an erroneous compiler error (&#8220;<code>Capture of 'code' with non-sendable type '() async throws(Failure) -&gt; Success' in a @Sendable closure</code>&#8220;). Assigning to a variable lets us apply <code>nonisolated(unsafe)</code> to disable the incorrect compiler diagnostic.</li>



<li>Several key aspects happen on this line:
<ul class="wp-block-list">
<li>It&#8217;s important to use a detached task, in case we&#8217;re already running in an isolated context (e.g. the MainActor) as we&#8217;re going to block the current thread waiting on the task to finish, via the semaphore.</li>



<li>The task logically needs to be run at the current task&#8217;s priority (or higher) in order to ensure it does actually run (re. priority inversion problems), although I&#8217;m not sure that technically matters here since we&#8217;re blocking in a non-await way anyway. One could use <code>Task.<a href="https://developer.apple.com/documentation/swift/task/currentpriority" data-wpel-link="external" target="_blank" rel="external noopener">currentPriority</a></code> here, but I&#8217;ve chosen to hard-code the highest priority (<code><a href="https://developer.apple.com/documentation/swift/taskpriority/userinitiated" data-wpel-link="external" target="_blank" rel="external noopener">userInitiated</a></code>) because it&#8217;s not great to block (in a non-await manner) on async code; although async code isn&#8217;t <em>necessarily</em> slow, I feel it&#8217;s wise to eliminate task prioritisation as a variable.</li>



<li>This closure must be explicitly marked as <code>@Sendable</code> as by default the compiler mistakenly infers it to be <em>not</em> <code>@Sendable</code>, even though all closure arguments to <code>Task</code> initialisers have to be <code>@Sendable</code>.  The compiler diagnostics in this case are frustratingly obtuse and misleading (although the sad saving grace is that this is a relatively common problem, so once you hit it enough times you start to develop a spidey sense for it).</li>
</ul>
</li>



<li>This otherwise pointless second <code>Task</code> is critical to prevent <code>withoutActuallyEscaping</code> from crashing.<br><br><code>withoutActuallyEscaping</code> basically relies on reference-counting &#8211; it records the retain count of its primary argument going in (<code>code</code> in this case) and compares that to the retain count going out &#8211; if they disagree, it crashes. There&#8217;s no way to disable or directly work around this<sup data-fn="ed8f4dd0-bc9b-4237-bf6f-fe6dad9cc344" class="fn"><a href="#ed8f4dd0-bc9b-4237-bf6f-fe6dad9cc344" id="ed8f4dd0-bc9b-4237-bf6f-fe6dad9cc344-link">3</a></sup>.<br><br>That&#8217;s a problem here because if we just signal the semaphore in the first task, right before exiting the task, we have a race &#8211; maybe the task <em>will</em> actually exit before the signal is acted on (<code>semaphore.<a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/2016071-wait" data-wpel-link="external" target="_blank" rel="external noopener">wait</a>()</code> returns and allows execution to exit the <code>withoutActuallyEscaping</code> block), but maybe it won&#8217;t. Since the task is retaining the closure, it <em>must</em> exit before we wake up from the semaphore and exit the <code>withoutActuallyEscaping</code> block, otherwise crash.<br><br>The only way I found to <em>ensure</em> the problematic task has fully exited &#8211; after hours of experimenting, covering numerous methods &#8211; is to wait for it in a <em>second</em> task. Surprisingly, the second task &#8211; despite having a strong reference to the first task &#8211; seemingly doesn&#8217;t prevent the first task from being cleaned up. This makes me suspicious, but despite extensive testing I&#8217;m unable to get <code>withoutActuallyEscaping</code> to crash when using this workaround.</li>



<li>There&#8217;s no practical way to avoid this forced unwrap, even though it&#8217;s impossible for it to fail unless something goes <em>very</em> wrong with Swift&#8217;s built-ins (like <code>withoutActuallyEscaping</code> and <code>Task</code>).<br><br>If you don&#8217;t wish to use typed throws, you could unwrap it more gently and throw an error of your own type if it&#8217;s nil, but it&#8217;s extra work and a loss of type safety for something that realistically cannot happen.</li>
</ol>


<ol class="wp-block-footnotes"><li id="fca8ab2f-af18-4e50-8bde-9aa3e28a1927">Specifically meaning &#8220;not run through Swift Concurrency, as async functions / closures&#8221;.  Lots of APIs will execute the callback on the main thread, which is the most difficult case, but even those that execute on a user-specified GCD queue aren&#8217;t helpful here &#8211; at least, <a href="https://github.com/swiftlang/swift/issues/74626" data-wpel-link="external" target="_blank" rel="external noopener">not until <code>assumeIsolated</code> actually works</a>. <a href="#fca8ab2f-af18-4e50-8bde-9aa3e28a1927-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="3f7186da-728e-41f5-b12b-6b7ca7456625">Incidentally, Wikipedia seems to think the canonical version of the joke is about spherical cows, but I&#8217;ve only ever heard it about chickens.  Indeed <a href="https://www.science.org/doi/10.1126/science.182.4119.1296.c" data-wpel-link="external" target="_blank" rel="external noopener">the very first known instance of the joke</a> used chickens, and all the pop-culture uses of it that I could find use chickens (most notably <a href="https://www.youtube.com/watch?v=Id0Ppz4OBKE" data-wpel-link="external" target="_blank" rel="external noopener">the ninth episode of The Big Bang Theory</a>). <a href="#3f7186da-728e-41f5-b12b-6b7ca7456625-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="ed8f4dd0-bc9b-4237-bf6f-fe6dad9cc344">There&#8217;s no <code>unsafeWithoutActuallyEscaping</code> that forgoes the runtime checking, nor any environment variables or similar that influence it; the check is <em>always</em> included and cannot be disabled at runtime.  Even when it&#8217;s unnecessary or &#8211; as in this case &#8211; outright erroneous.<br><br>Nor is there a way to replicate <code>withoutActuallyEscaping</code>&#8216;s core functionality of merely adding <code>@escaping</code> to the closure&#8217;s signature (e.g. via <code>unsafeBitCast</code> or similar) because it&#8217;s a special interaction with the compiler&#8217;s escape checker which is evaluated purely at compile-time (whether a closure is escaping or not is not actually encoded into the output binary nor memory representation of closures &#8211; the only time escapingness ever leaks into the binary is when you use <code>withoutActuallyEscaping</code> and the compiler inserts the special runtime assertion). <a href="#ed8f4dd0-bc9b-4237-bf6f-fe6dad9cc344-link" aria-label="Jump to footnote reference 3">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/calling-swift-concurrency-async-code-synchronously-in-swift/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8351</post-id>	</item>
		<item>
		<title>NSCopyObject, the griefer that keeps on griefing</title>
		<link>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/</link>
					<comments>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 16 Jul 2024 00:42:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[ARC]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[copy(with:)]]></category>
		<category><![CDATA[fixupCopiedIvars]]></category>
		<category><![CDATA[NeXT]]></category>
		<category><![CDATA[NSAnimation]]></category>
		<category><![CDATA[NSCell]]></category>
		<category><![CDATA[NSCopying]]></category>
		<category><![CDATA[NSImageRep]]></category>
		<category><![CDATA[Objective-C]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8281</guid>

					<description><![CDATA[NSCopyObject is a very old Foundation function - pre-dating Mac OS X entirely; from the NeXT era - that was originally basically just memcpy, but now it's complicated. A lot more complicated… <a class="read-more-link" href="https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code><a href="https://developer.apple.com/documentation/foundation/1587928-nscopyobject" data-wpel-link="external" target="_blank" rel="external noopener">NSCopyObject</a></code> is a very old Foundation function &#8211; pre-dating Mac OS X entirely; from the NeXT era &#8211; that was <em>originally</em> basically just <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/memcpy.3.html" data-wpel-link="external" target="_blank" rel="external noopener">memcpy</a></code>, but now it&#8217;s complicated.  A lot more complicated.</p>



<h2 class="wp-block-heading">What <code>NSCopyObject</code> does</h2>



<p>Its implementation <em>currently</em> starts with essentially:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="id NSCopyObject(id object, NSUInteger extraBytes, NSZone *zone) {
    if (nil == object) {
        return nil;
    }
    
    id copy = object_copy(object, extraBytes);
    object_setClass(copy, objc_opt_class(object));
    return copy;
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">id</span><span style="color: #000000"> </span><span style="color: #795E26">NSCopyObject</span><span style="color: #000000">(</span><span style="color: #0000FF">id</span><span style="color: #000000"> object, </span><span style="color: #267F99">NSUInteger</span><span style="color: #000000"> extraBytes, </span><span style="color: #267F99">NSZone</span><span style="color: #000000"> *zone) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> (</span><span style="color: #0000FF">nil</span><span style="color: #000000"> == object) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">nil</span><span style="color: #000000">;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">id</span><span style="color: #000000"> copy = </span><span style="color: #795E26">object_copy</span><span style="color: #000000">(object, extraBytes);</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">object_setClass</span><span style="color: #000000">(copy, </span><span style="color: #795E26">objc_opt_class</span><span style="color: #000000">(object));</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> copy;</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…where <a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L9057" data-wpel-link="external" target="_blank" rel="external noopener">object_copy</a> et al are part of <a href="https://github.com/apple-oss-distributions/objc4" data-wpel-link="external" target="_blank" rel="external noopener">the Objective-C runtime</a>.  <code>object_copy</code> and its callees are not trivial, so I won&#8217;t repeat them here.  The key parts are:</p>



<ol class="wp-block-list">
<li>The malloc in <code><a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L8981" data-wpel-link="external" target="_blank" rel="external noopener">_class_createInstance</a></code> (allocate space for the copy).</li>



<li>The <code>memmove</code> in <code>object_copy</code> (naively copy the raw bytes over).</li>



<li>The call from <code>object_copy</code> to <code><a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-class.mm#L535" data-wpel-link="external" target="_blank" rel="external noopener">fixupCopiedIvars</a></code> (half-heartedly attempt to fix the damage).</li>
</ol>



<p><code>fixupCopiedIvars</code> is notable.  It was added by necessity when <a href="https://en.wikipedia.org/wiki/Automatic_Reference_Counting" data-wpel-link="external" target="_blank" rel="external noopener">ARC</a> was introduced to the Objective-C runtime, in Mac OS X 10.6 (Snow Leopard) in 2006.  ARC added metadata to Objective-C classes to convey which instance variables were retain-counted object references, so that it could manage them automagically at runtime (not just for copying objects, but more importantly for deallocating them).  <code>fixupCopiedIvars</code> uses that metadata to identify things it has to retain (strongly or weakly) in the new copy.</p>



<p>So that should work great, right?  The copy operation increments the retain count of all shared objects the new copy references, like you&#8217;d expect?</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="340" height="266" src="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif" alt="Grumpy Cat frowning, with the caption &quot;NO&quot;." class="wp-image-8282" srcset="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif 340w, https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no-256x200.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no@2x.avif 680w" sizes="(max-width: 340px) 100vw, 340px" /></figure>
</div>


<p>That metadata is incomplete.  It only works for Objective-C ivars managed by ARC.  i.e. <em>not</em> C++ ivars or Swift stored properties, nor even Objective-C ivars that aren&#8217;t using ARC<sup data-fn="cf3bfb82-cbf6-40c4-817f-3092a63f4021" class="fn"><a href="#cf3bfb82-cbf6-40c4-817f-3092a63f4021" id="cf3bfb82-cbf6-40c4-817f-3092a63f4021-link">1</a></sup>.</p>



<h2 class="wp-block-heading">But I don&#8217;t use <code>NSCopyObject</code>…?</h2>



<p>Almost nobody <em>intentionally</em> uses <code>NSCopyObject</code>, but your superclass might, and therefore you might.  Ever subclassed <code><a href="https://developer.apple.com/documentation/appkit/nscell" data-wpel-link="external" target="_blank" rel="external noopener">NSCell</a></code> or <code><a href="https://developer.apple.com/documentation/appkit/nsanimation" data-wpel-link="external" target="_blank" rel="external noopener">NSAnimation</a></code>, for example?</p>



<p><a href="https://forums.swift.org/t/why-would-deinit-be-called-when-retain-count-is-non-zero/72924" data-wpel-link="external" target="_blank" rel="external noopener">I happened to hit this</a> when subclassing <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code> (and I&#8217;m very grateful to <a href="https://forums.swift.org/u/ksluder" data-wpel-link="external" target="_blank" rel="external noopener">Kyle Sluder</a> for so quickly identifying the problem &#8211; it could have taken me forever to figure it out, otherwise).</p>



<p>If your superclass uses <code>NSCopyObject</code>, it&#8217;s now your problem just as much as if you&#8217;d used <code>NSCopyObject</code> directly, whether you like it or not.</p>



<p>And even more problematically, whether you <em>know</em> it or not.  If your superclass is defined by a 3rd party framework / library, or anything that&#8217;s closed source, you might have no idea whether it uses <code>NSCopyObject</code> currently.  Worse, you have no control over whether it will or will not use it in future (though anyone that <em>adds</em> a use of <code>NSCopyObject</code> at this point had better hope the atheists are right).</p>



<h2 class="wp-block-heading">So how do I defend against <code>NSCopyObject</code>?</h2>



<h3 class="wp-block-heading">Objective-C</h3>



<p>Pre-ARC it used to be <em>relatively</em> easy to work around this, in Objective-C.  You &#8220;just&#8221; had to manually <code><a href="https://developer.apple.com/documentation/objectivec/1418956-nsobject/1571946-retain?language=objc" data-wpel-link="external" target="_blank" rel="external noopener">retain</a></code> all your subclasses&#8217; reference ivars &#8211; and manually copy some others, like non-ref-counted mutable or mortal buffers, etc.</p>



<p>But that generally isn&#8217;t possible with ARC &#8211; under which you cannot explicitly call <code>retain</code>.  Worse:</p>



<ul class="wp-block-list">
<li>There&#8217;s still <a href="https://www.mikeash.com/pyblog/friday-qa-2010-08-27-defensive-programming-in-cocoa.html" data-wpel-link="external" target="_blank" rel="external noopener">prominent</a> guides scattered about the web that push you unequivocally to use <code>retain</code>, which is not just impossible to do directly under ARC, but flat-out <em>wrong</em> even if you do figure out one of the &#8220;clever&#8221; ways to do it (you&#8217;ll end up <em>over</em>-retaining your ARC-managed references, causing memory leaks).</li>



<li>There&#8217;s also <a href="https://dohle.wordpress.com/2012/05/21/hello-world/" data-wpel-link="external" target="_blank" rel="external noopener">pages</a> lingering on the web that claim that merely turning on ARC will magically solve the problem (it <em>might</em>, but it&#8217;s not a panacea).</li>
</ul>



<p><a href="https://wiki.herzbube.ch/index.php/LearningObjectiveC#Object_copy" data-wpel-link="external" target="_blank" rel="external noopener">Some</a> <a href="https://robnapier.net/implementing-nscopying" data-wpel-link="external" target="_blank" rel="external noopener">guides</a> specify a better method, which is to manually zero out the copied object&#8217;s ivars and then repopulate them via formal property setters.  That actually works with or without ARC, although it may break &#8211; causing memory leaks &#8211; if the superclass ever stops using <code>NSCopyObject</code> (or if <code>NSCopyObject</code> ever gets upgraded to understand reference-counted ivars that it currently does not).  It&#8217;s also only possible in Objective-C because Swift doesn&#8217;t provide direct access to instance variables.</p>



<p>Keep in mind that any reference-typed ivars which are not strong or weak Objective-C objects managed by ARC will still, always need to be handled manually.  e.g. pointers to manually-managed memory buffers.</p>



<h3 class="wp-block-heading">Swift</h3>



<p>Ironically, Swift&#8217;s attempts to prevent incorrect code actually make it harder to write correct code in this case.  What you <em>want</em> to do is &#8211; like the Objective-C implementation &#8211; to just zero out the references and re-assign them like normal properties.  Zeroing them out <em>without triggering a release</em> basically undoes the mistaken <code>memcpy</code> that <code>NSCopyObject</code> did.  But Swift won&#8217;t let you.</p>



<p>Worse, <a href="https://github.com/swiftlang/swift/issues/47333" data-wpel-link="external" target="_blank" rel="external noopener">this has been known</a> for most of Swift&#8217;s existence and nothing has been done about it.</p>



<p>Simply setting the property to <code>nil</code> will cause it to be erroneously released, which may immediately deallocate the object and ultimately cause a crash or memory corruption.  Even if it doesn&#8217;t happen to deallocate the object, it&#8217;ll negate the retain you do during the assignment, making all your effort moot.</p>



<figure class="wp-block-pullquote"><blockquote><p>Strictly-speaking, the only safe thing to do is override <code><a href="https://developer.apple.com/documentation/foundation/nscopying/1410311-copy" data-wpel-link="external" target="_blank" rel="external noopener">copy(with:)</a></code> and not call super, but rather create a new instance from scratch.</p></blockquote></figure>



<p>That&#8217;s pretty heavy-handed, though, and not always possible (e.g. <code>NSImageRep</code>, as used by e.g. <code>NSBitmapImageRep</code>, does some special magic in its copy implementation which you cannot practically replicate).</p>



<p>It appears that the best you can do is <em>assume</em> the superclass will always use <code>NSCopyObject</code>, if it does currently, and just manually increment the retain count.  Like Objective-C with ARC, the language &amp; standard library really don&#8217;t want you to actually do this, but at least in Swift it&#8217;s relatively straightforward:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="override func copy(with zone: NSZone? = nil) -&gt; Any {
    let result = super.copy(with: zone)
    
    if result.myProperty === self.myProperty {
        _ = Unmanaged.passRetained(myProperty)
    } else {
        result.myProperty = self.myProperty
    }
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">copy</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000"> </span><span style="color: #001080">zone</span><span style="color: #000000">: NSZone? = </span><span style="color: #0000FF">nil</span><span style="color: #000000">) -&gt; </span><span style="color: #267F99">Any</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">copy</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000">: zone)</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> result.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> === </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #001080">_</span><span style="color: #000000"> = </span><span style="color: #267F99">Unmanaged</span><span style="color: #000000">.</span><span style="color: #795E26">passRetained</span><span style="color: #000000">(myProperty)</span></span>
<span class="line"><span style="color: #000000">    } </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        result.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">myProperty</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>The conditional <em>might</em> help protect you if the superclass stops using <code>NSCopyObject</code> in future &#8211; in that case, it&#8217;ll <em>probably</em> cause <code>myProperty</code> to default to nil (or to be assigned to some other instance, which you can discard), in which case you just want to assign to it normally.</p>



<p>In the interim &#8211; while <code>NSCopyObject</code> is in use, at least &#8211; the <code>myProperty</code> pointer will be copied verbatim and you have to assume it requires the extra, manual retain.  It&#8217;s <em>not</em> future-proof &#8211; it&#8217;s possible for the superclass to copy the pointer verbatim <em>and</em> increment the retain count for you &#8211; but at least in that case you &#8220;merely&#8221; get a memory leak, rather than a crash or memory corruption.</p>



<h2 class="wp-block-heading">Do as Apple says, not as Apple does</h2>



<p>The most frustrating part of all of this is that this is entirely Apple&#8217;s fault.  Sure, you can argue it&#8217;s not their fault that NeXT added this vile function to Foundation; that Apple &#8220;merely&#8221; inherited it and were &#8220;forced&#8221; to keep for backwards compatibility.  But it&#8217;s <em>entirely</em> Apple&#8217;s choice to have kept using it all this time, in their core frameworks, even while they&#8217;ve been telling everyone else to never use it.</p>



<p><code>NSCopyObject</code> has been a known problem-maker pretty much forever &#8211; it was a terrible idea right from the outset.  Blindly copying the bytes of an object instance, and just hoping that somehow that works correctly &#8211; in an <em>object-oriented</em> language derived from Smalltalk where <a href="https://developer.apple.com/documentation/foundation/nsnumber" data-wpel-link="external" target="_blank" rel="external noopener">even numbers are often reference types</a> &#8211; is farcical.</p>



<p>The introduction of ARC (in 2008) didn&#8217;t really help anything, as although it changed <code>NSCopyObject</code> to properly retain <em>ARC</em>-managed ivars, it did nothing for non-ARC-managed ivars (remember that ARC can be enabled in one library but not in another, and libraries can subclass each others&#8217; classes).</p>



<p><code>NSCopyObject</code> has been officially deprecated since 2012:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The NSCopyObject() function has been deprecated. It has always been a dangerous function to use, except in the implementation of copy methods<sup data-fn="19a637fd-3fbb-43d3-a9c0-29896c849e94" class="fn"><a href="#19a637fd-3fbb-43d3-a9c0-29896c849e94" id="19a637fd-3fbb-43d3-a9c0-29896c849e94-link">2</a></sup>, and only then with care.</p>
<cite><a href="https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_8Notes" data-wpel-link="external" target="_blank" rel="external noopener">Foundation Release Notes for OS X 10.8 Mountain Lion and iOS 6</a></cite></blockquote>



<p>…though Apple officially told everyone not to use it in 2008:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>This function is dangerous and very difficult to use correctly. It&#8217;s [sic] use as part of -copyWithZone: by any class that can be subclassed, is highly error prone. This function is known to fail for objects with embedded retain count ivars, singletons, and C++ ivars, and other circumstances.</p>
<cite><a href="https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_6Notes" data-wpel-link="external" target="_blank" rel="external noopener">Foundation Release Notes for Mac OS X 10.6 Snow Leopard</a></cite></blockquote>



<p>And this was all still a decade or more after it was known that <code>NSCopyObject</code> was fundamentally evil, e.g. <a href="https://www.mulle-kybernetik.com/weblog/2004/argh_wasted_two_hours_on_stupi.html" data-wpel-link="external" target="_blank" rel="external noopener">NSCell</a>, and <a href="https://mail.gnu.org/archive/html/discuss-gnustep/2000-09/msg00097.html" data-wpel-link="external" target="_blank" rel="external noopener">GnuStep&#8217;s broken NSControl</a>.</p>



<p>And yet, Apple <em>still</em> use <code>NSCopyObject</code> themselves <em>to this very day</em>, in their own applications and frameworks &#8211; including major frameworks like AppKit that almost all 3rd party developers rely on.  <code>NSCell</code> is <em>still</em> broken, three decades later, as is <code>NSImage</code> &amp; <code>NSImageRep</code>, and <code>NSAnimation</code>.  Most of those are <em>explicitly designed to be subclassed</em>, despite Apple&#8217;s own very clear instructions to never mix subclassing with <code>NSCopyObject</code>.</p>



<p>Admittedly it&#8217;s not trivial for Apple to remove the <code>NSCopyObject</code> use &#8211; alas, <em>because</em> people have had to code myriad hacky workarounds to it, Apple now has to be careful not to break those workarounds.  That might even preclude fixing the existing code paths; it might require a <em>replacement</em> copy mechanism.  Which leads to…</p>



<h2 class="wp-block-heading">Tangent: NSCopying considered harmful</h2>



<p>The big driver of <code>NSCopyObject</code> use has long been <code><a href="https://developer.apple.com/documentation/foundation/nscopying" data-wpel-link="external" target="_blank" rel="external noopener">NSCopying</a></code>.  Classes that intend to be subclassed &#8211; but also semantically should support copying i.e. <code>NSCopying</code> &#8211; have long been making the mistake of thinking that means using <code>NSCopyObject</code>.  One need only read the NSCopying documentation, even <a href="https://preterhuman.net/macstuff/techpubs/macosx/System/Library/Frameworks/Foundation.framework/Versions/C/Resources/English.lproj/Documentation/Reference/ObjC_classic/Protocols/NSCopying.html" data-wpel-link="external" target="_blank" rel="external noopener">from before Mac OS X was even publicly released</a>, to see how dangerously fragile and error-prone <code>NSCopying</code> has always been.</p>



<p>Compounding the problem is that <code>NSCopying</code> <em>doesn&#8217;t work, by default, on subclasses</em>.  You <em>have</em> to override <code>copy(with:)</code> in every subclass<sup data-fn="712b988a-5cac-484c-9eaf-fc22bc3afc25" class="fn"><a href="#712b988a-5cac-484c-9eaf-fc22bc3afc25" id="712b988a-5cac-484c-9eaf-fc22bc3afc25-link">3</a></sup>, but the compiler does not enforce this, because in Objective-C (and alas Swift) protocol conformance is <em>assumed</em> inherited even when it cannot correctly be without explicit, extra work by the subclass.</p>


<ol class="wp-block-footnotes"><li id="cf3bfb82-cbf6-40c4-817f-3092a63f4021">Yes, it&#8217;s still possible to this day to write Objective-C without using ARC &#8211; <code><a href="https://clang.llvm.org/docs/AutomaticReferenceCounting.html#id8" data-wpel-link="external" target="_blank" rel="external noopener">-fno-objc-arc</a></code> / <code><a href="https://developer.apple.com/documentation/xcode/build-settings-reference#Objective-C-Automatic-Reference-Counting" data-wpel-link="external" target="_blank" rel="external noopener">CLANG_ENABLE_OBJC_ARC</a></code>.  There might even be valid (albeit unfortunate) use-cases for having to do so, such as for performance.<br><br>And even with ARC, it&#8217;s of course possible to have pointers to things which aren&#8217;t <code>NSObject</code>s and therefore aren&#8217;t handled by ARC, such as raw malloc allocations. <a href="#cf3bfb82-cbf6-40c4-817f-3092a63f4021-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="19a637fd-3fbb-43d3-a9c0-29896c849e94">This is false and always has been (that it was safe to use in copy methods).  Apple&#8217;s false statements in the deprecation notices may ironically have caused even <em>more</em> instances of people using <code>NSCopyObject</code>. <a href="#19a637fd-3fbb-43d3-a9c0-29896c849e94-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="712b988a-5cac-484c-9eaf-fc22bc3afc25">Any and all that add retain-counted ivars, Swift stored properties, or ivars of C++ types that have destructors. <a href="#712b988a-5cac-484c-9eaf-fc22bc3afc25-link" aria-label="Jump to footnote reference 3">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8281</post-id>	</item>
		<item>
		<title>When all you have is a Core Data, everything looks like…</title>
		<link>https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/</link>
					<comments>https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 24 Jun 2024 23:32:00 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Core Data]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[NSCoding]]></category>
		<category><![CDATA[Shark]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[SwiftData]]></category>
		<category><![CDATA[Time Profile]]></category>
		<category><![CDATA[XML]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8235</guid>

					<description><![CDATA[Reading SwiftData vs Realm: Performance Comparison reminded me of an anecdote from my days working on Shark, at Apple. I don&#8217;t really remember the timing &#8211; sometime between 2006 and 2010 &#8211; but presumably around 2006 as I recall it was when Core Data was still relatively new. For whatever reason, there was a huge&#8230; <a class="read-more-link" href="https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Reading <a href="https://www.emergetools.com/blog/posts/swiftdata-vs-realm-performance-comparison" data-wpel-link="external" target="_blank" rel="external noopener">SwiftData vs Realm: Performance Comparison</a> reminded me of an anecdote from my days working on <a href="https://leopard-adc.pepas.com/documentation/DeveloperTools/Conceptual/SharkUserGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005233-CH1-DontLinkElementID_6" data-wpel-link="external" target="_blank" rel="external noopener">Shark</a>, at Apple.</p>



<p>I don&#8217;t really remember the timing &#8211; sometime between 2006 and 2010 &#8211; but presumably around 2006 as I recall it was when <a href="https://en.wikipedia.org/wiki/Core_Data" data-wpel-link="external" target="_blank" rel="external noopener">Core Data</a> was still relatively new.  For whatever reason, there was a huge push internal to Apple to use Core Data <em>everywhere</em>.  People were running around all over the place asking &#8220;can it be made to use Core Data?&#8221;, for Apple&#8217;s frameworks and applications.</p>



<p>Keep in mind that Core Data at that time was similar to <a href="https://developer.apple.com/documentation/swiftdata" data-wpel-link="external" target="_blank" rel="external noopener">SwiftData</a> now &#8211; very limited functionality, and <em>chock full</em> of bugs.  But of course it&#8217;s the nature of &#8216;shiny&#8217; new things that their proponents think it&#8217;s the second coming and the cure for all ills.</p>



<p>So, I recall sitting down with a couple of folks from the Core Data team, that were there to see if Shark could adopt Core Data.  A little like letting the missionaries in, if only out of morbid curiosity.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1200" height="675" src="https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data.avif" alt="Still from the scene in Orgazmo with the Mormon Missionaries greeting a homeowner at their door and asking &quot;Have you heard the good news about Core Data?&quot;." class="wp-image-8240" srcset="https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data.avif 1200w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-256x144.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-1024x576.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-768x432.avif 768w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data@2x.avif 2400w, https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data-256x144@2x.avif 512w" sizes="(max-width: 1200px) 100vw, 1200px" /></figure>



<p>Have you heard the good news?  Core Data is here to save your very data.  It&#8217;s effortless and divine and its unintuitive, thread-unsafe API will definitely not be the bane of all its users for the next fifteen years.</p>



<p>Jokes aside, they were in fact earnestly curious if Shark could use Core Data, instead of its own purpose-built binary formats, for storing &amp; querying its profiling data.  It was perhaps the classic case of naively underestimating the complexity of a foreign domain.  By my recollection, they assumed our profiling data was just a small handful of homogenous, relatively trivial records.  &#8220;At second N, the program ran the function named XYZ&#8221; or somesuch.</p>



<p>I think we (Shark engineers) tried to be open-minded and kind.  We were sceptical, but you never know until you actually look.  We could see some potential for a more general query capability, for example.  But of course the first and most obvious hurdle was: how well does Core Data handle sizeable numbers of records?  Oh yes, was the response, it&#8217;s great even with tens of thousands of records.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1128" height="480" src="https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot.avif" alt="Still image of the pawn shop scene from Star Trek IV (The Voyage Home) showing Kirk &amp; Spock responding to the offer of $100 for the antique spectacles with &quot;Is that a lot?&quot;." class="wp-image-8236" srcset="https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot.avif 1128w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-256x109.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-1024x436.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-768x327.avif 768w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot@2x.avif 2256w, https://wadetregaskis.com/wp-content/uploads/2024/06/star-trek-iv-the-voyage-home-is-that-a-lot-256x109@2x.avif 512w" sizes="auto, (max-width: 1128px) 100vw, 1128px" /></figure>



<p>We asked how it did with tens of <em>millions</em> of records, and that was pretty much the end of the conversation.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Background on the Time Profile data structure</summary>
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>For context, the data in a Shark Time Profile (for example) was basically an array of samples, where each sample records the process &amp; thread IDs, and the callstack (expressed as an array of pointer-sized values; the first being the current PC and the rest the return addresses found by walking back up the thread&#8217;s stack).</p>



<p>Callstacks back then were relatively small, by modern standards &#8211; this was predominately C/C++/Objective-C code which tended to be far simpler in its structure than e.g. Swift; <em>way</em> fewer closures (blocks), no async suspension points to split logical functions up into numerous implementation functions, etc.  So the average was probably something in the low tens.  A hundred frames was considered a <em>big</em> callstack (which is sadly funny in hindsight, given that&#8217;s trivial by e.g. SwiftUI&#8217;s standards 😒).</p>



<p>A useful profile had at least thousands of such samples, and typical profiles were in the tens to hundreds of thousands (the latter usually for All Threads States profiles, particularly those of the whole system).  Some profiles could run into the millions or tens of millions (it&#8217;s not always easy or predictable as to when a performance problem will exhibit itself, so recording sometimes had to start early and run long).</p>



<p>I&#8217;m pretty sure Shark used <code><a href="https://developer.apple.com/documentation/foundation/nscoding" data-wpel-link="external" target="_blank" rel="external noopener">NSCoding</a></code> for the overall serdes, but a lot of that serdes was of huge chunks of (as far as <code>NSCoding</code> was concerned) arbitrary bytes. The file format was overall fairly efficient (though I don&#8217;t recall it ever using explicit data compression, nor even delta encoding for callstacks).</p>
</div></div>
</details>



<p>It wasn&#8217;t just the volume of data, it was also the dramatic difference in representation efficiency. The in-memory representation in Shark was basically as efficient as it could be &#8211; basically just arrays of compact structs, sometimes with pointers to other arrays (which might share a <code>malloc</code> block to avoid the overhead of small allocations) which were usually just of <code>uint32_t</code> or <code>uint64_t</code>. The most important operations &#8211; indexing to an arbitrary point in the profile&#8217;s timeline, then scanning forward over the data &#8211; were about as fast as they can possibly be.</p>



<p>In contrast, Core Data would have required an entire <em>object</em> (<code><a href="https://developer.apple.com/documentation/coredata/nsmanagedobject" data-wpel-link="external" target="_blank" rel="external noopener">NSManagedObject</a></code> subclass) for at least every sample, if not every <code>uintXX_t</code> in the callstack (depending on how &#8216;pure&#8217; you wanted the design to be). It would have increased memory usage by at least an order of magnitude &#8211; and Shark already struggled with big profiles on the hardware of the day, which typically had just a couple of GiB of RAM. Even the most trivial operations &#8211; like reading the data in from disk and iterating it sequentially would have been <em>thousands</em> of times slower.</p>



<p>In defence of the Core Data folks in the meeting &#8211; and I don&#8217;t remember who specifically it was &#8211; they never tried to misrepresent or exaggerate what Core Data could do.  I seem to recall them being quite nice people.  But as soon as we started explaining the type and volume of data that we worked with, they clearly gave up on any kind of pitch.  Core Data was designed for <em>developer convenience</em>, not runtime efficiency or performance.</p>



<p>It&#8217;s never ceased to surprise and disappoint me how many folks try to arbitrarily apply generalised data storage systems &#8211; <em>particularly</em> SQLite and MySQL, or wrappers thereover.  Usually for the same reasons &#8211; perceived convenience to them, right now, not necessarily efficiency (nor the convenience of their successors).</p>



<p>I guess by modern standards SQLite is considered efficient and fast, but &#8211; hah &#8211; <em>back in my day</em> SQLite was what you used when you didn&#8217;t have time to write your own, <em>efficient and fast</em> persistent data management system.</p>



<p>See also JSON and its older sister XML. 😔</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/when-all-you-have-is-a-core-data-everything-looks-like/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/06/orgazmo-mormon-missionaries-have-you-heard-the-good-news-about-core-data.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8235</post-id>	</item>
		<item>
		<title>Swift on a Raspberry Pi (in 2024)</title>
		<link>https://wadetregaskis.com/swift-on-a-raspberry-pi-in-2024/</link>
					<comments>https://wadetregaskis.com/swift-on-a-raspberry-pi-in-2024/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 24 May 2024 19:20:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[buildSwiftOnARM]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[FutureJones]]></category>
		<category><![CDATA[Raspbian]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Swift Community Apt Repository]]></category>
		<category><![CDATA[Swift-Arm]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8202</guid>

					<description><![CDATA[Five years ago installing Swift on a Raspberry Pi &#8211; or really any non-Apple platform &#8211; was fairly involved. Compared to getting a Raspberry Pi working to begin with it was easy, but still a far cry from apt install swift. Sadly it&#8217;s still not quite that easy (and some Python package is squatting on&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swift-on-a-raspberry-pi-in-2024/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Five years ago installing Swift on a Raspberry Pi &#8211; or really <em>any</em> non-Apple platform &#8211; was <a href="https://wadetregaskis.com/swift-on-raspberry-pi/" data-wpel-link="internal">fairly involved</a>.  Compared to getting a Raspberry Pi working to begin with it was easy, but still a far cry from <code>apt install swift</code>.</p>



<p>Sadly it&#8217;s still not <em>quite</em> that easy (and some Python package is squatting on the <code>swift</code> package name 🤨).</p>



<p>You can still install from source, in principle, although I&#8217;m not aware of any <em>current</em> instructions on how to do so.  The <a href="https://github.com/uraimo/buildSwiftOnARM" data-wpel-link="external" target="_blank" rel="external noopener">buildSwiftOnARM</a> project has seemingly been abandoned.  I haven&#8217;t tested if their now-years-old instructions still work for the latest versions of Swift (e.g. 5.10, 6.0).</p>



<p>But, you shouldn&#8217;t need to build from source anymore &#8211; now you have many better options:</p>



<h2 class="wp-block-heading">Swift.org (Apple-provided options)</h2>



<ul class="wp-block-list">
<li>If you happen to be using a <code>yum</code>-centric platform, like RHEL / Amazon Linux / CentOS, you can (finally!) just <a href="https://www.swift.org/install/linux/#installation-via-rpm" data-wpel-link="external" target="_blank" rel="external noopener">install RPMs</a>.</li>



<li>If you happen to use one of <a href="https://www.swift.org/platform-support/" data-wpel-link="external" target="_blank" rel="external noopener">the officially supported Linux distributions</a>, you can <a href="https://www.swift.org/install/linux/#installation-via-docker" data-wpel-link="external" target="_blank" rel="external noopener">install via Docker</a>.  Alas many major distros are notably absent from that list, like Debian<sup data-fn="bf7d2063-1944-4bd3-8081-5ce94b1243ed" class="fn"><a href="#bf7d2063-1944-4bd3-8081-5ce94b1243ed" id="bf7d2063-1944-4bd3-8081-5ce94b1243ed-link">1</a></sup> (and therefore Raspbian). 😔<br><br>It&#8217;s unfortunate that <a href="https://www.swift.org/install/linux/" data-wpel-link="external" target="_blank" rel="external noopener">Swift.org pushes this as the preferred way</a> to install Swift on Linux.  I avoid Docker because it tends to just make everything more complex (and error-prone), and encourage bad software practices (&#8220;it works in my docker image 🖕&#8221;).
<ul class="wp-block-list">
<li>You <em>can</em> bypass the Docker requirement via the relatively primitive method of <a href="https://www.swift.org/install/linux/#installation-via-tarball" data-wpel-link="external" target="_blank" rel="external noopener">splatting a tarball into your system</a>.  It works, it&#8217;s pretty quick, but it&#8217;s a maintenance hassle (no version management, no reliable update method, etc).</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">Swift Community Apt Repository</h2>



<p>Thanks to the efforts of community volunteers &#8211; in particular the folks at <a href="https://futurejones.com" data-wpel-link="external" target="_blank" rel="external noopener">FutureJones</a> &#8211; there is the <a href="https://swiftlang.xyz" data-wpel-link="external" target="_blank" rel="external noopener">Swift Community Apt Repository</a>, which fills the massive void left by Apple&#8217;s apparent hatred for <code>apt</code>-based distros:</p>



<ol class="wp-block-list">
<li><code>curl -s https://archive.swiftlang.xyz/install.sh | sudo bash</code></li>



<li><code>sudo apt install swiftlang</code></li>



<li><a href="https://www.youtube.com/watch?v=c3pa3HCNHGI" data-wpel-link="external" target="_blank" rel="external noopener">There is no step 3!</a></li>
</ol>



<p><a href="https://swiftlang.xyz/user-guide" data-wpel-link="external" target="_blank" rel="external noopener">More details</a> (including the list of supported distros).</p>



<p>It&#8217;s a shame that it falls to third parties to make Swift on Linux easy (on major distros, at least).  Don&#8217;t forget to <a href="https://ko-fi.com/futurejones" data-wpel-link="external" target="_blank" rel="external noopener">support them</a> if you benefit from their work!</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ FutureJones also run <a href="https://swift-arm.com" data-wpel-link="external" target="_blank" rel="external noopener">Swift-Arm</a>, <a href="https://swift-arm.com/category/news" data-wpel-link="external" target="_blank" rel="external noopener">which implies</a> that only Swift 5.8 (and older) are supported.  But in fact 5.10 is available &#8211; it seems they&#8217;ve just forgotten / chosen to stop posting about newly supported Swift versions.</p>
</div></div>


<ol class="wp-block-footnotes"><li id="bf7d2063-1944-4bd3-8081-5ce94b1243ed">Though it appears <a href="https://github.com/apple/swift-docker/commit/1263ae112a1fd4b3445215c1f86994f161775b5c" data-wpel-link="external" target="_blank" rel="external noopener">Debian support is in the works</a>. <a href="#bf7d2063-1944-4bd3-8081-5ce94b1243ed-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swift-on-a-raspberry-pi-in-2024/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2019/10/Swift-on-Raspberry-Pi.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8202</post-id>	</item>
		<item>
		<title>The only usable ByteCountFormatStyle is decimal</title>
		<link>https://wadetregaskis.com/the-only-usable-bytecountformatstyle-is-decimal/</link>
					<comments>https://wadetregaskis.com/the-only-usable-bytecountformatstyle-is-decimal/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 18 May 2024 20:43:10 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[binary]]></category>
		<category><![CDATA[binary prefix]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[ByteCountFormatStyle]]></category>
		<category><![CDATA[decimal]]></category>
		<category><![CDATA[FormatStyle]]></category>
		<category><![CDATA[SI units]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8165</guid>

					<description><![CDATA[Swift makes it relatively easy to format numbers as byte counts, with appropriate suffixes to indicate units and generally sensible auto-selection of scale factors. e.g.: 1 kB (in English &#8211; results may vary depending on locale) This is just a small subset of Swift&#8217;s FormatStyle-based formatting capabilities, with which I have a bit of a&#8230; <a class="read-more-link" href="https://wadetregaskis.com/the-only-usable-bytecountformatstyle-is-decimal/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Swift makes it relatively easy to format numbers as byte counts, with appropriate suffixes to indicate units and generally sensible auto-selection of scale factors.  e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #098658">1234</span><span style="color: #000000">.</span><span style="color: #795E26">formatted</span><span style="color: #000000">(.</span><span style="color: #795E26">byteCount</span><span style="color: #000000">(</span><span style="color: #795E26">style</span><span style="color: #000000">: .</span><span style="color: #001080">decimal</span><span style="color: #000000">))</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p>1 kB</p><cite>(in English &#8211; results may vary depending on locale)</cite></blockquote></figure>



<p>This is just a small subset of Swift&#8217;s <code><a href="https://developer.apple.com/documentation/foundation/formatstyle" data-wpel-link="external" target="_blank" rel="external noopener">FormatStyle</a></code>-based formatting capabilities, with which <a href="https://wadetregaskis.com/fucking-formatstyle/" data-wpel-link="internal">I have a bit of a love-hate relationship</a> even when they&#8217;re working correctly.</p>



<p>Alas, they don&#8217;t always work correctly; some of these formatters contain egregious bugs.</p>



<p>In particular, <code>ByteCountFormatStyle</code> pretends to support multiple numeric bases &#8211; decimal and binary &#8211; but it doesn&#8217;t, because what it renders for binary is:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #098658">1234</span><span style="color: #000000">.</span><span style="color: #795E26">formatted</span><span style="color: #000000">(.</span><span style="color: #795E26">byteCount</span><span style="color: #000000">(</span><span style="color: #795E26">style</span><span style="color: #000000">: .</span><span style="color: #001080">binary</span><span style="color: #000000">))</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p>1 kB</p><cite>(in English &#8211; results may vary depending on locale)</cite></blockquote></figure>



<p>Note how it still uses <em>decimal</em> units, &#8220;kB&#8221;.  Decimal is not binary.  I mean, duh, right?  But apparently Apple don&#8217;t know this.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ <a href="https://en.wikipedia.org/wiki/Binary_prefix" data-wpel-link="external" target="_blank" rel="external noopener">Binary prefix</a> abbreviations are always two characters, the second always &#8220;i&#8221;.  In this case, &#8220;KiB&#8221;<sup data-fn="360480d9-2d8c-44ac-ae55-be1417b55320" class="fn"><a href="#360480d9-2d8c-44ac-ae55-be1417b55320" id="360480d9-2d8c-44ac-ae55-be1417b55320-link">1</a></sup>.  They&#8217;re easy to remember because they use the same first letter as their decimal cousins, e.g. &#8220;Ti&#8221; &amp; &#8220;T&#8221; for tebi and tera.  Their derivation is really simple &#8211; take the first two letters of the decimal cousin, e.g. &#8220;te&#8221;, and the first two letters from &#8220;binary&#8221;, &#8220;bi&#8221; &#8211; voilà, &#8220;tebi&#8221;.  To abbreviate, just drop the middle letters and use titlecase.</p>
</div></div>



<h2 class="wp-block-heading">So what?</h2>



<p>While the above example is tolerable because, given the rounding applied, the numeric result (&#8220;1&#8221;) is technically corrected in both bases, see what happens when you use larger values:</p>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Input number</th><th class="has-text-align-center" data-align="center"><code><a href="https://developer.apple.com/documentation/foundation/bytecountformatstyle/style/decimal" data-wpel-link="external" target="_blank" rel="external noopener">decimal</a></code></th><th class="has-text-align-center" data-align="center"><code><a href="https://developer.apple.com/documentation/foundation/bytecountformatstyle/style/binary" data-wpel-link="external" target="_blank" rel="external noopener">binary</a></code></th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right">1,000</td><td class="has-text-align-center" data-align="center">1 kB</td><td class="has-text-align-center" data-align="center">1,000 bytes</td></tr><tr><td class="has-text-align-right" data-align="right">1,024</td><td class="has-text-align-center" data-align="center">1 kB</td><td class="has-text-align-center" data-align="center">1 kB</td></tr><tr><td class="has-text-align-right" data-align="right">1,000,000</td><td class="has-text-align-center" data-align="center">1 MB</td><td class="has-text-align-center" data-align="center">977 kB</td></tr><tr><td class="has-text-align-right" data-align="right">1,048,576</td><td class="has-text-align-center" data-align="center">1 MB</td><td class="has-text-align-center" data-align="center">1 MB</td></tr><tr><td class="has-text-align-right" data-align="right">1,000,000,000</td><td class="has-text-align-center" data-align="center">1 GB</td><td class="has-text-align-center" data-align="center">953.7 MB</td></tr><tr><td class="has-text-align-right" data-align="right">1,073,741,824</td><td class="has-text-align-center" data-align="center">1.07 GB</td><td class="has-text-align-center" data-align="center">1 GB</td></tr><tr><td class="has-text-align-right" data-align="right">1,000,000,000,000</td><td class="has-text-align-center" data-align="center">1 TB</td><td class="has-text-align-center" data-align="center">931.32 GB</td></tr><tr><td class="has-text-align-right" data-align="right">1,099,511,627,776</td><td class="has-text-align-center" data-align="center">1.1 TB</td><td class="has-text-align-center" data-align="center">1 TB</td></tr><tr><td class="has-text-align-right" data-align="right">1,000,000,000,000,000</td><td class="has-text-align-center" data-align="center">1 PB</td><td class="has-text-align-center" data-align="center">909.49 TB</td></tr><tr><td class="has-text-align-right" data-align="right">1,125,899,906,842,624</td><td class="has-text-align-center" data-align="center">1.13 PB</td><td class="has-text-align-center" data-align="center">1 PB</td></tr></tbody></table></figure>



<p>Once you get up to non-trivial byte counts, you start to get different results even with the heavy rounding that&#8217;s applied by default.  By the time you&#8217;re talking about GBs the error is on the order of 10%.  And the error just gets proportionately larger as the input number increases.  <a href="https://en.wikipedia.org/wiki/Binary_prefix#Comparison_of_binary_and_decimal_prefixes" data-wpel-link="external" target="_blank" rel="external noopener">Wikipedia has a neat little table showing this</a>.</p>



<h2 class="wp-block-heading">Okay, but that&#8217;s just <code>binary</code>, what about <code><a href="https://developer.apple.com/documentation/foundation/bytecountformatstyle/style/file" data-wpel-link="external" target="_blank" rel="external noopener">file</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/bytecountformatstyle/style/memory" data-wpel-link="external" target="_blank" rel="external noopener">memory</a></code>?</h2>



<p>Those are just aliases to <code>decimal</code> and <code>binary</code>, respectively.  So <code>memory</code> is right out.</p>



<p>You might think <code>file</code> is still okay, because it essentially means <code>decimal</code>.  But that&#8217;s only <em>currently</em>.  The entire point of its existence is to allow its behaviour to change over time, following whatever convention Apple believes is most appropriate for files.  In the past that was in fact <code>binary</code>, as noted previously (pre-Snow Leopard).  It might be <code>binary</code> again in future.  So it&#8217;s dangerous to use since you can neither know what units it&#8217;s going to use nor whether Apple will have fixed their formatters by the time it switches off of <code>decimal</code>.</p>



<h2 class="wp-block-heading">But context will save us!</h2>



<p>Probably not.  Go look at file sizes in the macOS Finder.  Can you tell whether they&#8217;re actual decimal (as they appear) or binary?</p>



<p>They&#8217;re <em>decimal</em>, but they <em>used</em> to be binary, <a href="https://www.macworld.com/article/199831/snow_leopard_math.html" data-wpel-link="external" target="_blank" rel="external noopener">up until Mac OS X Snow Leopard (10.6)</a>.  And the Finder used the <em>same</em> (decimal) unit abbreviations throughout.  So at least it&#8217;s using the correct units now &#8211; almost by accident &#8211; but it created a confused transition and history.</p>



<p>Worse and more presently pertinent, not everything Finder-like uses decimal, nor correct units when they don&#8217;t.  e.g. <a href="https://superuser.com/questions/1519532/why-do-mac-and-synology-report-different-file-sizes" data-wpel-link="external" target="_blank" rel="external noopener">Synology&#8217;s products</a>, not to mention countless websites.  That creates a lot of confusion which bleeds across into even well-behaved software.</p>



<p>Point being, you both can&#8217;t rely on context to help, because context includes things you can&#8217;t control like other software, and by getting the units wrong you&#8217;re making it worse for everyone, not just yourself.</p>



<h2 class="wp-block-heading">Why is this still happening?</h2>



<p>Going back twenty years, this kind of error &#8211; confusing decimal with binary w.r.t. unit prefixes &#8211; was both the norm and <em>somewhat</em> excusable &#8211; binary prefixes were <a href="https://en.wikipedia.org/wiki/IEC_60027" data-wpel-link="external" target="_blank" rel="external noopener">only formally standardised upon in 1999</a>, so it&#8217;s only to be expected that it will take some time for everyone to adopt them.</p>



<p>But it&#8217;s been a quarter of a century already.  There is absolutely no excuse anymore for getting this wrong.</p>


<ol class="wp-block-footnotes"><li id="360480d9-2d8c-44ac-ae55-be1417b55320">If you&#8217;re paying attention you&#8217;ll have noticed &#8220;k&#8221; vs &#8220;K&#8221;, i.e. the difference in case.  This is not arbitrary &#8211; it&#8217;s because units have to be distinct to be functional, and &#8220;K&#8221; is the abbreviation for Kelvin (and apparently wins because it&#8217;s one of <a href="https://en.wikipedia.org/wiki/SI_base_unit" data-wpel-link="external" target="_blank" rel="external noopener">the seven <em>base</em> physical units</a>, from which pretty much all other physical units are derived).<br><br>&#8220;Ki&#8221; is fine because it&#8217;s a distinct prefix (and &#8220;i&#8221; is not a valid abbreviation by itself so there&#8217;s no potential confusion about whether it means &#8220;Kelvin • i&#8221; or &#8220;kibi&#8221;). <a href="#360480d9-2d8c-44ac-ae55-be1417b55320-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/the-only-usable-bytecountformatstyle-is-decimal/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8165</post-id>	</item>
		<item>
		<title>Fucking FormatStyle</title>
		<link>https://wadetregaskis.com/fucking-formatstyle/</link>
					<comments>https://wadetregaskis.com/fucking-formatstyle/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 18 May 2024 20:25:05 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[ByteCountFormatStyle]]></category>
		<category><![CDATA[FormatStyle]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8181</guid>

					<description><![CDATA[I have a bit of a love-hate relationship with Swift&#8217;s FormatStyle-based formatting capabilities. 42% (in English &#8211; results may vary depending on locale) On the pro side: But the cons are rough: All in all, there&#8217;s a reason fuckingformatstyle.com exists (and don&#8217;t forget to tip!).]]></description>
										<content:encoded><![CDATA[
<p>I have a bit of a love-hate relationship with Swift&#8217;s <code><a href="https://developer.apple.com/documentation/foundation/formatstyle" data-wpel-link="external" target="_blank" rel="external noopener">FormatStyle</a></code>-based formatting capabilities.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #795E26">Text</span><span style="color: #000000">(progress.</span><span style="color: #795E26">formatted</span><span style="color: #000000">(.</span><span style="color: #001080">percent</span><span style="color: #000000">.</span><span style="color: #795E26">precision</span><span style="color: #000000">(.</span><span style="color: #795E26">significantDigits</span><span style="color: #000000">(</span><span style="color: #098658">2</span><span style="color: #000000">))))</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p>42%</p><cite>(in English &#8211; results may vary depending on locale)</cite></blockquote></figure>



<p>On the pro side:</p>



<ul class="wp-block-list">
<li>They&#8217;re better than rolling your own formatting. Not just in terms of convenience, but in terms of correctness &#8211; they support localisation, for example.  &#8220;42%&#8221; in English, &#8220;٤٢٪؜&#8221; in العربية (Arabic), &#8220;৪২%&#8221; in অসমীয়া (Assamese), &#8220;42 %&#8221; in Deutsch (German), etc.</li>



<li>They&#8217;re terser than using their otherwise more powerful cousins the <code><a href="https://developer.apple.com/documentation/foundation/formatter" data-wpel-link="external" target="_blank" rel="external noopener">Formatter</a></code>s, as they support a &#8220;fluent&#8221; style of property-based access, which tends to read more naturally and usually avoids having to define variables to hold the formatter.</li>
</ul>



<p>But the cons are rough:</p>



<ul class="wp-block-list">
<li>They almost always break Xcode&#8217;s auto-complete, which is a problem since their syntax is non-trivial and unintuitive.</li>



<li>They&#8217;re hard to understand &#8211; and to even find in Apple&#8217;s official documentation &#8211; because there&#8217;s so many protocols and indirection involved.<br><br>It&#8217;s particularly hard to tell where the inexplicable gaps are. e.g. <code><a href="https://developer.apple.com/documentation/swift/double" data-wpel-link="external" target="_blank" rel="external noopener">Double</a></code> doesn&#8217;t support <code><a href="https://developer.apple.com/documentation/foundation/bytecountformatstyle" data-wpel-link="external" target="_blank" rel="external noopener">ByteCountFormatStyle</a></code>, even though logically it should <em>and</em> Xcode will sometimes auto-complete as if it does. Worse, there&#8217;s often no valid compiler diagnostic if you try to use them together &#8211; you just get a long hang and eventually the dreaded &#8220;unable to type check in a reasonable time&#8221; cop-out.</li>
</ul>



<p>All in all, there&#8217;s a reason <a href="https://fuckingformatstyle.com" data-wpel-link="external" target="_blank" rel="external noopener">fuckingformatstyle.com</a><sup data-fn="ab1d5ba0-795f-4f9c-81d6-b44c94903168" class="fn"><a href="#ab1d5ba0-795f-4f9c-81d6-b44c94903168" id="ab1d5ba0-795f-4f9c-81d6-b44c94903168-link">1</a></sup> exists (and <a href="https://ko-fi.com/ampersandsoftworks" data-wpel-link="external" target="_blank" rel="external noopener">don&#8217;t forget to tip</a>!).</p>


<ol class="wp-block-footnotes"><li id="ab1d5ba0-795f-4f9c-81d6-b44c94903168">There&#8217;s also the alias <a href="https://goshdarnformatstyle.com" data-wpel-link="external" target="_blank" rel="external noopener">goshdarnformatstyle.com</a> if you want to pretend anybody doesn&#8217;t know exactly what you actually mean.  I assure you that the stronger expletive is by far the most appropriate, once you have experience with <code>FormatStyle</code>. <a href="#ab1d5ba0-795f-4f9c-81d6-b44c94903168-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/fucking-formatstyle/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8181</post-id>	</item>
		<item>
		<title>Copy-on-write on APFS</title>
		<link>https://wadetregaskis.com/copy-on-write-on-apfs/</link>
					<comments>https://wadetregaskis.com/copy-on-write-on-apfs/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 16 May 2024 21:14:10 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Education]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[APFS]]></category>
		<category><![CDATA[clonefile]]></category>
		<category><![CDATA[copy-on-write]]></category>
		<category><![CDATA[copyfile]]></category>
		<category><![CDATA[FileManager]]></category>
		<category><![CDATA[Foundation]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8138</guid>

					<description><![CDATA[APFS (like many modern file systems but unlike its predecessor HFS+) supports copy-on-write. This means you can logically copy a file &#8211; it looks and behaves like a distinct file &#8211; but it doesn&#8217;t immediately copy the file&#8217;s contents on disk &#8211; it merely shares them with the original. Only if and as you modify&#8230; <a class="read-more-link" href="https://wadetregaskis.com/copy-on-write-on-apfs/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><a href="https://en.wikipedia.org/wiki/Apple_File_System" data-wpel-link="external" target="_blank" rel="external noopener">APFS</a> (like many modern file systems but unlike its predecessor <a href="https://en.wikipedia.org/wiki/HFS_Plus" data-wpel-link="external" target="_blank" rel="external noopener">HFS+</a>) supports <a href="https://eclecticlight.co/2017/06/23/what-is-copy-on-write-and-how-is-it-good/" data-wpel-link="external" target="_blank" rel="external noopener">copy-on-write</a>.  This means you can <em>logically</em> copy a file &#8211; it <em>looks</em> and <em>behaves</em> like a distinct file &#8211; but it doesn&#8217;t <em>immediately</em> copy the file&#8217;s contents on disk &#8211; it merely shares them with the original.  Only if and as you modify either version do they start to diverge on disk, with APFS dynamically allocating new storage for the modified parts<sup data-fn="fd9542d5-ca23-49bc-ae00-3d2b57caf906" class="fn"><a href="#fd9542d5-ca23-49bc-ae00-3d2b57caf906" id="fd9542d5-ca23-49bc-ae00-3d2b57caf906-link">1</a></sup>.</p>



<p>This is kind of a sister function to hard links, which similarly avoid copying the file&#8217;s contents <em>but</em> where modifications apply to <em>all</em> copies.  See also <a href="https://eclecticlight.co/2019/01/05/aliases-hard-links-symlinks-and-copies-in-mojaves-apfs/" data-wpel-link="external" target="_blank" rel="external noopener">this article on the differences</a>, including versus aliases and symlinks.</p>



<p>Copy-on-write is beneficial for several reasons:</p>



<ul class="wp-block-list">
<li>Copies don&#8217;t take up any significant space (just whatever tiny amount is necessary for their metadata).</li>



<li>The initial copy operation is practically instantaneous (just a few small metadata writes &amp; updates).</li>



<li>Deferring (if not entirely avoiding) the actual disk I/O reduces wear on the disk.</li>



<li>The copies can share common segments, saving disk space even when they&#8217;re not ultimately identical copies.</li>
</ul>



<p>It does have some potential downsides:</p>



<ul class="wp-block-list">
<li>You don&#8217;t get the increased data redundancy and error resilience that <em>actual</em> copies provide (although if you&#8217;re aiming for data redundancy or backup, you should be using separate physical disks anyway).</li>



<li>It can make subsequent modifications of the file slower, as even just modifying a single byte can trigger the actual copy to be performed.</li>
</ul>



<p>And some basic limitations:</p>



<ul class="wp-block-list">
<li>It&#8217;s only supported on APFS (and <em>maybe</em> additional file systems added by 3rd party extensions, but I haven&#8217;t tested nor can I find any accounts of this).</li>



<li>It only works within individual volumes (it doesn&#8217;t work even between two volumes in the same APFS container, or sharing the same physical disk).</li>
</ul>



<p>Contrary to what I saw online in a few places, copy-on-write works on <em>all</em> APFS volumes, irrespective of whether they are backed by SSDs, HDDs, or some other type of storage.</p>



<h2 class="wp-block-heading">Should I use it?</h2>



<p>Yes!</p>



<p>For most purposes those downsides aren&#8217;t an issue, and the limitations merely mean that it&#8217;s wise to have a fallback option (of just copying the actual file contents) whenever copy-on-write isn&#8217;t available.  And many of the tools &amp; APIs fallback automatically (unless you explicitly require them not to, such as with <code>COPYFILE_CLONE_FORCE</code> to <code>copyfile</code>).</p>



<h2 class="wp-block-heading">How do I use it?</h2>



<figure class="wp-block-table aligncenter"><table><thead><tr><th>Method</th><th class="has-text-align-center" data-align="center">Uses copy-on-write<br>(where possible)</th><th class="has-text-align-center" data-align="center">Does actual copy<br>if copy-on-write<br> isn&#8217;t available</th></tr></thead><tbody><tr><td>cp</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td>cp -c</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td>ditto</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td>ditto &#8211;clone</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td>dd</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td>scp</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td>Finder Copy then Paste</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td>Finder Duplicate</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td>Finder ⌥-drag</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td><code><a href="https://www.manpagez.com/man/2/clonefile/" data-wpel-link="external" target="_blank" rel="external noopener">clonefile</a></code><sup data-fn="1bebdb6f-7e69-4ebb-bca6-692795e4e8f7" class="fn"><a href="#1bebdb6f-7e69-4ebb-bca6-692795e4e8f7" id="1bebdb6f-7e69-4ebb-bca6-692795e4e8f7-link">2</a></sup></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td><code><a href="https://keith.github.io/xcode-man-pages/copyfile.3.html" data-wpel-link="external" target="_blank" rel="external noopener">copyfile</a>(…, …, …, COPYFILE_DATA)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td><code><a href="https://keith.github.io/xcode-man-pages/copyfile.3.html" data-wpel-link="external" target="_blank" rel="external noopener">copyfile</a>(…, …, …, COPYFILE_CLONE)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td><code><a href="https://keith.github.io/xcode-man-pages/copyfile.3.html" data-wpel-link="external" target="_blank" rel="external noopener">copyfile</a>(…, …, …, COPYFILE_CLONE_FORCE)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/filemanager/1412957-copyitem" data-wpel-link="external" target="_blank" rel="external noopener">FileManager.copyItem(at:to:)</a></code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/filemanager/1407903-copyitem" data-wpel-link="external" target="_blank" rel="external noopener">FileManager.copyItem(atPath:toPath:)</a></code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr></tbody></table><figcaption class="wp-element-caption">This is accurate for macOS 14.5 (23F79).  The behaviour might vary across OS releases.</figcaption></figure>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h3 class="wp-block-heading">Testing method</h3>



<p>Not that this is interesting, just for posterity and to show my work a bit, in case I made a mistake.</p>



<p>I created large-enough files on each of my test volumes (APFS on SSD, APFS on HDD, HFS+ on SSD) that an actual copy would take tens of seconds at least, using <code>dd</code> e.g.:</p>



<figure class="wp-block-pullquote"><blockquote><p><code>dd if=/dev/random of=/tmp/bigfile oflag=direct status=progress bs=1k count=104857600</code></p></blockquote></figure>



<p>I then ran the various command line tools on these files, attempting to clone the file to a different name in the same folder, and observed disk I/O activity with Activity Monitor and iStat Menus.</p>



<p>For actual copy-on-writes I would observe that the program successfully concluded practically instantly, and there&#8217;d be at most a small blip of disk writes (for metadata modifications).</p>



<p>For failed copy-on-writes I would observe that the program would not exit promptly and I&#8217;d see voluminous disk writes (hundreds of megabytes to gigabytes per second, depending on the disk, sustained for many seconds until I was satisfied with the results and killed the test).</p>



<p>For API tests the overall approach was the same, but the APIs were invoked from inside <code>swift repl</code> where possible, and from a throw-away Swift script otherwise<sup data-fn="4f593842-632c-4279-83ba-435dde24c411" class="fn"><a href="#4f593842-632c-4279-83ba-435dde24c411" id="4f593842-632c-4279-83ba-435dde24c411-link">3</a></sup>.</p>


<ol class="wp-block-footnotes"><li id="fd9542d5-ca23-49bc-ae00-3d2b57caf906">I haven&#8217;t tested it, but as far as I&#8217;ve heard APFS does <em>not</em> actually check if the modifications actually diverge the files.  e.g. if you &#8220;modify&#8221; a byte of the file to the value it already has &#8211; a pointless but technically possible operation that leaves both copies still identical &#8211; APFS <em>will still copy the modified block</em>.<br><br>Furthermore, APFS &amp; Apple&#8217;s operating systems appear to have no tools (nor APIs) to deduplicate files &#8211; e.g. to detect full or partial copies and deduplicate their actual storage on disk.  Not even tools that you could invoke manually if you do the hard work of first determining that two files&#8217; contents are identical.<br><br>APFS / Apple&#8217;s operating systems rely entirely on user applications using copy-on-write explicitly and upfront. <a href="#fd9542d5-ca23-49bc-ae00-3d2b57caf906-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="1bebdb6f-7e69-4ebb-bca6-692795e4e8f7">Curiously, <code>clonefile</code> first shipped in macOS 10.12 (Sierra), <a href="https://www.manpagez.com/man/2/clonefile/" data-wpel-link="external" target="_blank" rel="external noopener">according to its man page</a>, which is the release <em>preceding</em> the introduction of APFS in macOS 10.13 (High Sierra).  Yet, as far as I can tell HFS+ doesn&#8217;t and never did support cloning &#8211; nor do any of the other file systems supported by macOS 10.12 (e.g. FAT16 &amp; FAT32, exFAT, NFS).  It&#8217;s possible it was just convenient for Apple to include it in the prior release as they were probably testing APFS with it internally, during APFS&#8217;s development.<br><br><strong>Update</strong>: <a href="https://mjtsai.com" data-wpel-link="external" target="_blank" rel="external noopener">Michael Tsai</a> <a href="https://mastodon.social/@mjtsai/112456863768132668" data-wpel-link="external" target="_blank" rel="external noopener">pointed out</a> that <a href="https://www.pcmag.com/news/what-macos-sierras-new-apfs-file-system-means-to-you" data-wpel-link="external" target="_blank" rel="external noopener">Sierra included APFS support in beta form</a> (e.g. you could create disk images with it, but not use it for a boot disk).  Thus why <code>clonefile</code> (and other APFS-related tools) were included in Sierra.  <a href="#1bebdb6f-7e69-4ebb-bca6-692795e4e8f7-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="4f593842-632c-4279-83ba-435dde24c411">While the C APIs worked just fine inside <code>swift repl</code> irrespective of what volumes I was targeting, the Foundation APIs oddly refused to work whenever the files were not on the boot volume, throwing &#8220;Operation not permitted&#8221; errors (NSCocoaErrorDomain 513, with no user info).  If it weren&#8217;t for the C APIs working just fine I&#8217;d think it&#8217;s a sandboxing issue, but clearly it&#8217;s not (and <code>swift repl -disable-sandbox</code> makes no difference). 🤔 <a href="#4f593842-632c-4279-83ba-435dde24c411-link" aria-label="Jump to footnote reference 3">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/copy-on-write-on-apfs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8138</post-id>	</item>
		<item>
		<title>Swift sucks at web serving… or does it?</title>
		<link>https://wadetregaskis.com/swift-sucks-at-web-serving-or-does-it/</link>
					<comments>https://wadetregaskis.com/swift-sucks-at-web-serving-or-does-it/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 16 May 2024 01:32:06 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Education]]></category>
		<category><![CDATA[BigInt]]></category>
		<category><![CDATA[FPM]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[kevents]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[kqueue]]></category>
		<category><![CDATA[macOS kernel]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[Numberick]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Swift Forums]]></category>
		<category><![CDATA[SwiftNIO]]></category>
		<category><![CDATA[Vapor]]></category>
		<category><![CDATA[web server]]></category>
		<category><![CDATA[wrk]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8061</guid>

					<description><![CDATA[A few weeks ago, Axel Roest published a simple web server comparison, that turned out to not be doing what it was thought to be doing. Figuring that out was a very interesting discussion that warrants a retrospective, to look at which parts were particularly helpful and which not so much. Tangentially, I want to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swift-sucks-at-web-serving-or-does-it/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>A few weeks ago, Axel Roest published <a href="https://tech.phlux.us/Juice-Sucking-Servers/" data-wpel-link="external" target="_blank" rel="external noopener">a simple web server comparison</a>, that turned out to not be doing what it was thought to be doing. Figuring that out was a very interesting discussion that warrants a retrospective, to look at which parts were particularly helpful and which not so much.</p>



<p>Tangentially, I want to highlight that Axel&#8217;s comparison is notable because he is interested in <em>efficiency</em>, not mere brute performance. The two are usually correlated but not always the same. He correctly noted that electricity is a major <em>and increasingly large</em> part of server costs (see <a href="https://wadetregaskis.com/the-cost-of-electrical-power-in-servers/" data-wpel-link="internal">my prior post</a> for why it&#8217;s even worse than you likely realise). That said, while he did take RAM and power measurements, his benchmark and analysis didn&#8217;t go into detail about energy efficiency.</p>



<h1 class="wp-block-heading">Benchmark method &amp; apparatus</h1>



<p>Axel wanted to see how a very simple web server performed in:</p>



<ul class="wp-block-list">
<li><a href="https://www.php.net/manual/en/install.fpm.php" data-wpel-link="external" target="_blank" rel="external noopener">FPM</a> w/ <a href="https://www.nginx.com" data-wpel-link="external" target="_blank" rel="external noopener">NGINX</a> (PHP).</li>



<li><a href="https://helidon.io" data-wpel-link="external" target="_blank" rel="external noopener">Helidon</a> (Kotlin / Java<sup data-fn="e7728698-06db-400a-a6b9-01a2ce4f3e5b" class="fn"><a href="#e7728698-06db-400a-a6b9-01a2ce4f3e5b" id="e7728698-06db-400a-a6b9-01a2ce4f3e5b-link">1</a></sup>).</li>



<li><a href="https://nodejs.org/en" data-wpel-link="external" target="_blank" rel="external noopener">Node.js</a> (JavaScript).</li>



<li><a href="https://vapor.codes" data-wpel-link="external" target="_blank" rel="external noopener">Vapor</a> (Swift).</li>
</ul>



<p>He was particularly interested in throughput &amp; latency vs RAM &amp; power usage. All are important metrics in their own right, but are most useful in light of each other.</p>



<p>He chose to use <a href="https://en.wikipedia.org/wiki/Fibonacci_sequence" data-wpel-link="external" target="_blank" rel="external noopener">Fibonacci sequence</a> calculation as the load. Choosing a load for any web server benchmark is always highly contentious, and not the focus of this post. Whether you think Fibonacci&#8217;s a good choice or not, read on to see why really it didn&#8217;t matter.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ People get hung up on how well benchmarks represent the so-called real world, but I think that&#8217;s often fruitless to argue about and also beside the point. What matters is whether the benchmark is <em>useful</em>. e.g. does it <em>inform</em> and <em>elucidate</em>?</p>
</div></div>



<p>He did use <em>very</em> old hardware, though &#8211; an Intel Core i3-550 from over a decade ago. Fortunately it didn&#8217;t turn out to materially impact the relative results nor behaviours of the benchmark, but it&#8217;s usually unwise to add unnecessary [potential] variables to your setup, like unusual hardware.</p>



<p>In my own debugging and profiling, I used my also very old 10-core iMac Pro. It&#8217;s at least a Xeon? 😅</p>



<h1 class="wp-block-heading">Benchmark results</h1>



<div class="wp-block-group is-content-justification-center is-layout-flex wp-container-core-group-is-layout-64b26803 wp-block-group-is-layout-flex">
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="800" height="586" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput.webp" alt="" class="wp-image-8063" style="object-fit:cover;width:400px;height:293px" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput.webp 800w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-256x188.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-768x563.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-256x188@2x.webp 512w" sizes="auto, (max-width: 800px) 100vw, 800px" /><figcaption class="wp-element-caption">Requests per second (Y) over concurrent requests (X).<br>From <a href="https://tech.phlux.us/Juice-Sucking-Servers/" data-wpel-link="external" target="_blank" rel="external noopener">Axel&#8217;s first post</a>.</figcaption></figure>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="800" height="617" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-failure-rate.webp" alt="" class="wp-image-8064" style="object-fit:cover;width:400px;height:293px" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-failure-rate.webp 800w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-failure-rate-256x197.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-failure-rate-768x592.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-failure-rate-256x197@2x.webp 512w" sizes="auto, (max-width: 800px) 100vw, 800px" /><figcaption class="wp-element-caption">Success rate (Y) over concurrent requests (X).<br>From <a href="https://tech.phlux.us/Juice-Sucking-Servers/" data-wpel-link="external" target="_blank" rel="external noopener">Axel&#8217;s first post</a>.</figcaption></figure>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="577" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-memory-usage.webp" alt="" class="wp-image-8076" style="object-fit:cover;width:400px;height:300px" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-memory-usage.webp 800w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-memory-usage-256x185.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-memory-usage-768x554.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-memory-usage-256x185@2x.webp 512w" sizes="auto, (max-width: 800px) 100vw, 800px" /><figcaption class="wp-element-caption">RAM usage (Y) over concurrent requests (X).<br>From <a href="https://tech.phlux.us/Juice-Sucking-Servers/" data-wpel-link="external" target="_blank" rel="external noopener">Axel&#8217;s first post</a>.</figcaption></figure>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="575" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-power-usage.webp" alt="" class="wp-image-8077" style="object-fit:cover;width:400px;height:300px" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-power-usage.webp 800w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-power-usage-256x184.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-power-usage-768x552.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-power-usage-256x184@2x.webp 512w" sizes="auto, (max-width: 800px) 100vw, 800px" /><figcaption class="wp-element-caption">Power usage (Y) over concurrent requests (X).<br>From <a href="https://tech.phlux.us/Juice-Sucking-Servers/" data-wpel-link="external" target="_blank" rel="external noopener">Axel&#8217;s first post</a>.</figcaption></figure>
</div>



<p>In words:</p>



<ul class="wp-block-list">
<li>Helidon (Kotlin / Java) had the highest throughput and lowest latency at low (and arguably more reasonable) loads, but used by far the most RAM, and the most power. Consequently it handled the most load before requests started failing (timing out).</li>



<li>Node.js (JavaScript) was qualitatively very similar to Helidon (Kotlin / Java) but less in all metrics &#8211; less throughput, less peak load capacity, but also less RAM and very slightly less power used.</li>



<li>FPM + NGINX (PHP) followed the pattern.</li>



<li>Vapor (Swift) did not &#8211; it had higher throughput than PHP yet requests started failing much sooner as load increased. It used the least RAM and least power, though, and kept on trucking irrespective of the load.</li>
</ul>



<p>Many people would have left it at that &#8211; obviously the results make sense for the first three (&#8220;everyone knows&#8221; that Kotlin / Java&#8217;s faster than JavaScript that&#8217;s faster than PHP) and Vapor / Swift apparently just isn&#8217;t fast and has weird reliability behaviours. QED, right?</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ Going in with a specific hypothesis can be helpful, but hypotheses can also end up being just biases. Be careful not to blindly accept apparent confirmation of the hypothesis. Similarly, beware subconscious hypotheses like &#8220;Kotlin / Java is faster than JavaScript&#8221;.</p>
</div></div>



<p>To his credit, Axel wasn&#8217;t so sure &#8211; he felt that the results he was seeing were suspicious, and <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583" data-wpel-link="external" target="_blank" rel="external noopener">he sought help from the Swift Forums</a> in explaining or correcting them.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>✅ Question your results. <em>Understand</em> them. It improves the quality, correctness, and usefulness of your work. <em>Why</em> something behaves the way it does is often more interesting and important than merely how it behaves.</p>



<p>On most platforms it&#8217;s pretty easy to at least do a time profile, and most often that&#8217;s all you need to understand what&#8217;s going on. On Apple platforms you can use <a href="https://www.avanderlee.com/debugging/xcode-instruments-time-profiler/" data-wpel-link="external" target="_blank" rel="external noopener">Instruments</a>, on Windows &amp; Linux tools like <a href="https://www.intel.com/content/www/us/en/docs/vtune-profiler/user-guide/2024-1/basic-hotspots-analysis.html" data-wpel-link="external" target="_blank" rel="external noopener">VTune</a>, among <a href="https://en.wikipedia.org/wiki/List_of_performance_analysis_tools" data-wpel-link="external" target="_blank" rel="external noopener">many other options</a>.</p>



<p>If need be, ask others for help, like Axel did.</p>
</div></div>



<p>While Axel did <em>suspect</em> something was wrong &#8211; noting the oddly small but persistent failure rate &#8211; he missed the most obvious <em>proof</em> of wrongness &#8211; logically impossible results. Doing 80,000 continuous concurrent streams of requests with ~98% of those requests completing within the two second time limit means the server must have a throughput of at least 39,000 requests per second. Yet the benchmark tool reported a mere ~8,000 requests per second.</p>



<p>Sadly, though <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/2" data-wpel-link="external" target="_blank" rel="external noopener">I pointed this out as the very first response to the thread</a>, it seemed to be overlooked by everyone (even myself!), even though it clearly fingered the benchmark tool itself as the problem (which is only partially correct, as we&#8217;ll see later, but in any case was the exact right place to start looking).</p>



<h1 class="wp-block-heading">Debugging the benchmark</h1>



<h2 class="wp-block-heading">Domain experts weigh in</h2>



<p>The Swift Forum post immediately attracted relevant people: folks that work on Vapor and NIO, and folks that have experience using them. However, ironically this didn&#8217;t initially help &#8211; they tended to assume the problem was in Vapor (or its networking library, <a href="https://github.com/apple/swift-nio" data-wpel-link="external" target="_blank" rel="external noopener">SwiftNIO</a>) or how Vapor was being configured. It turned out none of this was really true &#8211; there <em>was</em> <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/49" data-wpel-link="external" target="_blank" rel="external noopener">a small optimisation made to Vapor</a> as a result of all this, which did marginally improve performance (in specific circumstances), but ultimately Vapor &amp; NIO were not the problem, nor was the benchmark&#8217;s configuration and use of them.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ It can be all too easy to assume elaborate reasons when you know a lot about something. Don&#8217;t jump to conclusions. Check the most basic and foundational things <em>first</em>.</p>



<p>I say this with humility and I guess technically hypocrisy, because even as professional performance engineer (in the past) I&#8217;ve repeatedly made this mistake myself. We&#8217;re all particularly susceptible to this mistake.</p>
</div></div>



<p>There were some assertions that the results <em>were</em> plausible and just how Vapor performs, and that the &#8220;problem&#8221; was the choice of Vapor rather than some other web server framework (e.g. <a href="https://github.com/hummingbird-project/hummingbird" data-wpel-link="external" target="_blank" rel="external noopener">Hummingbird</a>).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ It&#8217;s not <em>wrong</em> to be interested in additional data, but be careful not to get distracted. Using Vapor was not in any way wrong or unhelpful &#8211; it is the most well-known and probably well-used web server framework in Swift. It might well be that other frameworks are better in some respects, but that&#8217;s a <em>different</em> comparison than what Axel performed.</p>
</div></div>



<p>Others similarly asserted that the results were plausible because Swift uses <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/" data-wpel-link="external" target="_blank" rel="external noopener">reference-counting</a> for memory management whereas PHP, JavaScript, and Kotlin / Java use garbage collection. It was presented as &#8220;common knowledge&#8221; that garbage collection has inherent benefits for some programs, like web servers, because it makes memory allocation super cheap.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ While it can be useful to speculate a little, in a brainstorming sense, don&#8217;t presume. A <em>lot</em> of mistakes have been made over the years because of this, like that &#8220;linked lists are faster than arrays&#8221; or &#8220;binary search is faster than linear search&#8221;, etc.</p>



<p>Remember that intuition is in large part presumptions and generalisations. That doesn&#8217;t make intuition useless, but always remember that it&#8217;s far from foolproof. Use it to generate hypotheses, not conclusions.</p>
</div></div>



<h2 class="wp-block-heading">Examining the load</h2>



<p>Even though it was clear that something was wrong with the actual measurements, a lot of the early discussion revolved around the load used (Fibonacci sequence calculation), particularly regarding whether it was:</p>



<h3 class="wp-block-heading">The &#8220;right&#8221; load</h3>



<p>A few folks asserted that the CPU-heavy nature of calculating Fibonacci numbers isn&#8217;t representative of web servers generally. Multiple people noted that &#8211; in the Swift implementation, at least &#8211; the majority of the CPU time was spent doing the Fibonacci calculation. Some felt this was therefore not a useful benchmark of Vapor itself.</p>



<p>A lot of this boiled down to <a href="https://en.wikipedia.org/wiki/No_true_Scotsman" data-wpel-link="external" target="_blank" rel="external noopener">the &#8220;no true Scotsman&#8221; problem</a>, which is very common in benchmarking, with a bit of <a href="https://en.wikipedia.org/wiki/Nirvana_fallacy#Perfect_solution_fallacy" data-wpel-link="external" target="_blank" rel="external noopener">perfect world logical fallacy</a> peppered in, trying to identify the One True Representative Benchmark. See the earlier point about fixating on such matters rather than whether the benchmark is <em>useful</em>.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ While it&#8217;s not necessarily wrong or unwise to evaluate how well a benchmark represents real world usage (whether generally or against specific cases), it&#8217;s an exercise that suffers from diminishing returns pretty quickly. It&#8217;s usually best to not quibble too much or too long, as long as the benchmark is in the ballpark.</p>



<p>You can always develop &amp; present your own benchmark(s), if you feel there are better or additional ways to go about it. Best of all, the existence of <em>both</em> the original benchmark and your benchmark(s) will be more useful than either alone, since you can compare and contrast them.</p>
</div></div>



<h3 class="wp-block-heading">A &#8220;fair&#8221; load</h3>



<p>Accusations were made pretty quickly that the benchmark is &#8220;unfair&#8221; to Swift because Swift doesn&#8217;t &#8211; it was asserted &#8211; have a properly-optimised &#8220;<a href="https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic" data-wpel-link="external" target="_blank" rel="external noopener">BigInt</a>&#8221; implementation, unlike all the other languages tested.</p>



<p>No real evidence was given for this. Even if it were true, it doesn&#8217;t invalidate the benchmark &#8211; in fact, it just makes the benchmark <em>more</em> successful because it&#8217;s then highlighted an area where Swift is lacking.</p>



<p>The BigInt library that Axel used, <a href="https://github.com/attaswift/BigInt" data-wpel-link="external" target="_blank" rel="external noopener">attaswift/BigInt</a>, is by far the most popular available for Swift, as judged by things like GitHub stars, forks, &amp; contributor counts, ranking in web &amp; GitHub searches, etc. There are <a href="https://swiftpackageindex.com/search?query=bigint" data-wpel-link="external" target="_blank" rel="external noopener">quite a few others</a>, though.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ There are multiple ways to approach a benchmark, all equally valid because they&#8217;re all useful. Axel chose to use popular packages, in <em>all</em> the languages he tested. That&#8217;s definitely fair. It&#8217;s also useful because it represents what the typical developer will do when building real web servers.</p>



<p>It&#8217;s often also interesting and useful to search out the <em>best</em> packages (whatever that may mean in context, such as fastest). That could represent what a more heavily optimised implementation might do. It <em>might</em> also better represent what is theoretical possible (<em>if</em> optimal packages exist already). Those are interesting things to explore too, just not what Axel happened to be doing.</p>



<p>You can see also more of my thoughts on Axel&#8217;s choice here, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/39" data-wpel-link="external" target="_blank" rel="external noopener">in the Swift Forums thread</a>.</p>
</div></div>



<p>It wasn&#8217;t until <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/46" data-wpel-link="external" target="_blank" rel="external noopener">actual evidence was presented</a>, that the discussion made progress.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ While it&#8217;s true that without the initial blind assertions actual data might never have been gathered, it would have been more effective and efficient to have just gathered the data at the start.</p>



<p>Data is better than supposition.</p>
</div></div>



<p>It was shown that in fact the BigInt implementation in question <em>was</em> significantly slower than it could be, because JavaScript&#8217;s implementation of addition was much faster. <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/62" data-wpel-link="external" target="_blank" rel="external noopener">Some additional simple tests</a> showed even wider performance gaps regarding the other key operation: rendering to strings. It was <em>that</em> data that turned out to be critical &#8211; <a href="https://github.com/apple/swift-foundation/pull/262" data-wpel-link="external" target="_blank" rel="external noopener">I myself happened to have implemented BigInt string rendering for Apple&#8217;s new Foundation</a>, <em>and</em> then <a href="https://github.com/apple/swift-foundation/pull/306" data-wpel-link="external" target="_blank" rel="external noopener">saw it dramatically optimised by</a> <a href="https://github.com/oscbyspro" data-wpel-link="external" target="_blank" rel="external noopener">Oscar Byström Ericsson</a>, whom has his own BigInt package for Swift, <a href="https://github.com/oscbyspro/Numberick" data-wpel-link="external" target="_blank" rel="external noopener">Numberick</a>. So I had a pretty darn good idea of where I might find a faster package… 😆</p>



<p>You can read more about that specific bit of serendipity <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/64" data-wpel-link="external" target="_blank" rel="external noopener">in the Swift Forums thread</a>.</p>



<p>It was trivial to do the package switch, and it quickly improved Vapor/Swift&#8217;s showing in the benchmark manyfold &#8211; in combination with some other simple and reasonable tweaks, it was <em>five times faster</em>!</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>✅ Axel&#8217;s benchmark taught a lot of people that <a href="https://github.com/oscbyspro/Numberick" data-wpel-link="external" target="_blank" rel="external noopener">Numberick</a> is much more performant than <a href="https://github.com/attaswift/BigInt" data-wpel-link="external" target="_blank" rel="external noopener">BigInt</a>, at least in some important operations (addition and string rendering). Granted that knowledge is a little bit niche in its utility, but it&#8217;s still a good outcome.</p>



<p>It also demonstrated that modifying in place can be faster than creating a copy, <em>even if</em> it means having to do a swap. i.e.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">a += b</span></span>
<span class="line"><span style="color: #795E26">swap</span><span style="color: #000000">(&amp;a, &amp;b)</span></span></code></pre></div>



<p>…instead of:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> c = a + b</span></span>
<span class="line"><span style="color: #000000">a = b</span></span>
<span class="line"><span style="color: #000000">b = c</span></span></code></pre></div>



<p>That&#8217;s a tidbit I had picked up through varied experiences, and <a href="https://wadetregaskis.com/swift-tip-the-swap-function/" data-wpel-link="internal">wrote about previously</a>. The <code><a href="https://developer.apple.com/documentation/swift/swap(_:_:)" data-wpel-link="external" target="_blank" rel="external noopener">swap</a></code> function in Swift is under-appreciated and under-utilised. This knowledge may seem esoteric but you&#8217;d be amazed how often it applies (a <em>lot</em> of programming is about combining data, after all).</p>
</div></div>



<p>Axel posted <a href="https://tech.phlux.us/Juice-Sucking-Servers-Part-Deux/" data-wpel-link="external" target="_blank" rel="external noopener">a follow-up with additional data</a> (with the aforementioned changes and optimisations). That showed Swift now beating out the other three frameworks / languages, with the highest throughput and lowest latency (and still the lowest RAM and power usage).</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="800" height="513" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-after-fixes.webp" alt="" class="wp-image-8071" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-after-fixes.webp 800w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-after-fixes-256x164.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-after-fixes-768x492.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput-after-fixes-256x164@2x.webp 512w" sizes="auto, (max-width: 800px) 100vw, 800px" /><figcaption class="wp-element-caption">From Axel&#8217;s follow-up post. X axis is the number of concurrent requests.</figcaption></figure>
</div>


<p>So, all done, right? Turns out, Vapor/Swift wins, yeah?</p>



<p>Well, maybe.</p>



<h3 class="wp-block-heading" id="do-these-improvements-apply-to-the-other-cases-too">Do these improvements apply to the other cases too?</h3>



<p>That is yet to be examined. Because only Swift seemed to be producing odd results, Axel only put the benchmark to the Swift community for deeper analysis. It&#8217;s quite possible that doing the same with the other web frameworks &amp; languages would similarly reveal potential improvements.</p>



<p>Still, the results are useful as they stand. Some simple and very plausible &#8211; even for a Swift beginner &#8211; optimisations made a big difference, though of course the biggest difference was simply using a different 3rd party package. There are a lot of useful lessons in that, both in the specifics as already covered and as general best practices.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ Benchmarks are rarely &#8220;done&#8221;, their results rarely &#8220;final&#8221;. At least if you permit optimisations or other changes. How do you <em>know</em> there&#8217;s not something still &#8220;unfair&#8221; about one of the cases?</p>



<p>Again, this speaks to the potential futility of trying to make &#8220;fair&#8221; benchmarks, and reiterates the practical benefit of simply trying to learn instead.</p>
</div></div>



<h2 class="wp-block-heading">…but… why is the success rate still weird?</h2>



<p>Despite the improved performance, a fundamental problem remained: <em>the numbers still didn&#8217;t make sense</em>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="800" height="512" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-success-rate-after-fixes.png" alt="" class="wp-image-8073" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-success-rate-after-fixes.png 800w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-success-rate-after-fixes-256x164.png 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-success-rate-after-fixes-768x492.png 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-success-rate-after-fixes-256x164@2x.png 512w" sizes="auto, (max-width: 800px) 100vw, 800px" /><figcaption class="wp-element-caption">From Axel&#8217;s follow-up post. X axis is the number of concurrent requests.</figcaption></figure>
</div>


<p>The success rates are <em>slightly</em> different but not materially &#8211; as concurrent requests go up, the throughput plateaus very quickly, yet success rate remains about the same. It&#8217;s exactly the same problem as at the outset &#8211; these results cannot possibly be correct.</p>



<p>Despite all the community&#8217;s efforts, we hadn&#8217;t actually figured out the real problem. We&#8217;d merely made Swift <em>look</em> better, without actually providing confidence in the accuracy of the results.</p>



<p>In fairness to myself, I was well aware that we weren&#8217;t done, I was just struggling to understand what was really going on, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/74" data-wpel-link="external" target="_blank" rel="external noopener">as I noted here</a>.</p>



<h2 class="wp-block-heading">Examining the benchmark tool</h2>



<p>While there&#8217;d been some tangential questions about <code><a href="https://github.com/wg/wrk" data-wpel-link="external" target="_blank" rel="external noopener">wrk</a></code>, the benchmarking tool Axel used, it had largely been ignored thus far.</p>



<p>Ironically (as you&#8217;ll soon see) Axel chose <code>wrk</code> specifically because <a href="https://tech.phlux.us/Juice-Sucking-Servers/#benchmarking-software" data-wpel-link="external" target="_blank" rel="external noopener">he didn&#8217;t like the behaviour he saw</a> with <a href="https://httpd.apache.org/docs/2.4/programs/ab.html" data-wpel-link="external" target="_blank" rel="external noopener">ApacheBench</a>. Mostly its lack of HTTP/1.1 connection reuse (a subjective but valid methodology choice on Axel&#8217;s part) but also because it sounds like he saw some inexplicable results from it too. In hindsight, that might have been a clue that something more pervasive was wrong.</p>



<p>In retrospect there were a few tangential comments in the Swift Forums thread that were on the right track, e.g.:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>…when a new connection comes in, the server needs to make a decision: It can</p>



<ul class="wp-block-list">
<li>Either accept the new connection immediately, slowing the existing connections down a little (because now there are more connections to service with the same resources as before)</li>



<li>Or it can prioritise the existing connections and slow the connection acceptance (increasing the latency of the first request in the new connection which now has to wait).</li>
</ul>
<cite><a href="https://forums.swift.org/u/johannesweiss/summary" data-wpel-link="external" target="_blank" rel="external noopener">Johannes Weiss</a>, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/77" data-wpel-link="external" target="_blank" rel="external noopener">Swift Forums post</a></cite></blockquote>



<p>As a little spoiler, it seems apparent that the other three web frameworks all accept incoming connections virtually immediately with priority over any existing connections &amp; request handling (even though they don&#8217;t necessarily attempt to <em>serve</em> all those connections&#8217; requests simultaneously). Vapor does not.</p>



<p>Suspicions did [correctly] develop around the opening of the connections themselves, which triggered testing with longer timeouts in a somewhat blind attempt to cover-up the &#8220;spurious&#8221; first moments of the test.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>❌ Trying to essentially just hide inconvenient results is unlikely to help. It may even be successful, which is the worst possible outcome because it&#8217;s basically just burying a time-bomb into the benchmark, <em>and</em> forgoing any real understanding &amp; potential knowledge to be gained from properly investigating the problem.</p>
</div></div>



<h3 class="wp-block-heading">Characterising the failure mode(s)</h3>



<p>Though admittedly I wasn&#8217;t <em>fully</em> conscious of what I was doing at the time, the next breakthrough came from simply <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/85" data-wpel-link="external" target="_blank" rel="external noopener">gathering more data and analysing it <em>qualitatively</em></a>. This helped in two key ways:</p>



<ul class="wp-block-list">
<li>It better defined and pinned down the circumstances in which things appear to go wrong with the benchmark itself.<br><br>It separated out a whole bunch of test configurations that seemingly weren&#8217;t interesting (as they behaved in line with intuition / expectations, and similarly across all four web servers).</li>
</ul>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>✅ When it doubt, try to better define the problem. Eliminate variables. Refine quantitative estimates. Make your life easier by eliminating things that don&#8217;t matter.</p>
</div></div>



<ul class="wp-block-list">
<li>It provided hints and potential insight into the nature of the problem.<br><br>It showed that there was some kind of variability (in time) in the benchmark&#8217;s behaviour, with three very distinct modes (including one which was basically the benchmark actually working as expected, the existence of which had been unknown until that point!).</li>
</ul>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>✅ There are <em>many</em> ways to approach a data set, in terms of analysis methods. It&#8217;s a good idea to always keep that in mind, and to try different analysis mindsets whenever you seem stuck (and also to further validate conclusions).</p>
</div></div>



<p>Interestingly although ultimately only tangentially, this modality finding prompted quite a few &#8220;me too!&#8221; responses from other folks, about a variety of use-cases involving Vapor <em>or</em> NIO. I took that as affirmation that I was onto something real, but in retrospect that should have been an even better clue: the fact that some people had seen this issue <em>without</em> Vapor involved &#8211; the only common denominator was NIO. Even though it turns out NIO itself wasn&#8217;t doing any wrong, it was on the right path to answers. <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/87" data-wpel-link="external" target="_blank" rel="external noopener">This was <em>specifically</em> pointed out to everyone</a>, even.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ Sometimes, it just comes down to needing to listen better.</p>
</div></div>



<h3 class="wp-block-heading">Overlooked clues</h3>



<p>At this point there were a bunch of discussions about <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/89" data-wpel-link="external" target="_blank" rel="external noopener">benchmark tool configuration</a>, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/91" data-wpel-link="external" target="_blank" rel="external noopener">hardware arrangement</a>, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/102" data-wpel-link="external" target="_blank" rel="external noopener">whether TLS should be used</a>, etc. I&#8217;m going to skim over it, because there&#8217;s not much to ultimately say about it &#8211; it turned out to not be on the right track in this case, or purely tangential, but it was entirely reasonable to investigate &amp; discuss those aspects. Such is debug life.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="600" height="600" src="https://wadetregaskis.com/wp-content/uploads/2024/05/Debug-Life-t-shirt.avif" alt="" class="wp-image-8109" style="object-fit:cover" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/Debug-Life-t-shirt.avif 600w, https://wadetregaskis.com/wp-content/uploads/2024/05/Debug-Life-t-shirt-256x256.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/Debug-Life-t-shirt-256x256@2x.avif 512w" sizes="auto, (max-width: 600px) 100vw, 600px" /><figcaption class="wp-element-caption">I couldn&#8217;t find evidence that anyone&#8217;s gotten the tattoo yet, but you can at least <a href="https://www.redbubble.com/i/t-shirt/Debug-Life-White-Typographic-Design-for-Thug-Programmers-by-ramiro/17317023.FB110" data-wpel-link="external" target="_blank" rel="external noopener">get the wardrobe</a>.</figcaption></figure>
</div>


<p>What&#8217;s interesting is that yet another key clue was mentioned in the Swift Forums thread, yet was overlooked because it was attributed incorrectly and the mechanics miscategorised:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Don&#8217;t test with more than 128 connections. You will get read errors. This is due to the file descriptor limit applied to each process on macOS. As&nbsp;<a href="https://forums.swift.org/u/johannesweiss" data-wpel-link="external" target="_blank" rel="external noopener">@johannesweiss</a>&nbsp;mentioned earlier the default for this is 256. You can change this but it involves disabling the System Integrity Protection.</p>
<cite><a href="https://forums.swift.org/u/adam-fowler/summary" data-wpel-link="external" target="_blank" rel="external noopener">Adam Fowler</a>, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/99" data-wpel-link="external" target="_blank" rel="external noopener">Swift Forums thread</a></cite></blockquote>



<p>The 128 connections &amp; read errors parts were spot on, in hindsight. But the rest was incorrect (it&#8217;s not about the file descriptor ulimit) and in particular the incorrect statement about having to disable SIP perhaps further distracted readers (corrections were posted in reply, which perhaps steered the thread away from what actually mattered).</p>



<p>I&#8217;m not sure what precisely the lesson is here… if Adam had better understood the behaviour he&#8217;d seen previously (re. 128 connections being the apparent limit) he might have been able to immediately point out one of the key problems. But who can say why he didn&#8217;t quite understand that limit correctly, or whether he should have. This sort of thing happens, and <em>maybe</em> it suggests a failure to properly diagnose problems previously, but mostly I&#8217;d just point out that the discrepancy here &#8211; between 128 and 256 &#8211; <em>should</em> have been noticed, and had it been questioned it would have accelerated progress towards the root cause.</p>



<p>Speaking just for myself, I think I (erroneously) dismissed Adam&#8217;s comment because I already knew that the default file descriptor limit is <em>not</em> actually 256 (it&#8217;s 2,560 on macOS, mostly) and so I assumed the <em>whole</em> comment was wrong and irrelevant.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ Partly wrong is not the same as completely wrong (let-alone useless).</p>
</div></div>



<p>Another clue was put forth, yet again essentially by accident (without understanding its significance, at the time):</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Yes, the reason I used&nbsp;<code>wrk</code>, is that it uses pipelining. That&#8217;s why ab (apachebench) had such terrible performance: it opened a new socket for each request. And then it overloaded the system by throwing</p>



<ul class="wp-block-list">
<li><code>socket: Too many open files</code></li>



<li><code>apr_socket_recv: Connection reset by peer&nbsp;</code></li>
</ul>



<p>errors.</p>



<p>I raised the&nbsp;<code>ulimit -n</code>&nbsp;to 10240, but still&nbsp;<code>apr_socket_recv: Connection reset by peer (104)</code>&nbsp;occurred occasionally.</p>
<cite><a href="https://forums.swift.org/u/axello/summary" data-wpel-link="external" target="_blank" rel="external noopener">Axel Roest</a>, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/122" data-wpel-link="external" target="_blank" rel="external noopener">Swift Forums thread</a></cite></blockquote>



<p>This hinted very directly at the second major problem, but it seems nobody in the forum thread realised it. I think there was still a pre-occupation with the file descriptor ulimit.</p>



<p>A little logic applied at the time of Axel&#8217;s comment <em>should</em> have revealed its mistaken presumption: that opening new TCP connections for each HTTP request will inevitably cause connection failures. Sure, it will if you give it enough concurrent connection attempts, but real-world web servers operate at <em>huge</em> loads that are basically one HTTP request per connection, without any significant reliability problems. In hindsight, it&#8217;s clear that Axel&#8217;s dismissal of this behaviour as in any way normal was a mistake &#8211; as was everyone else in the thread going along with that dismissal.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ If a tool isn&#8217;t working the way you expect, maybe that&#8217;s telling you something important. Just switching tools until you find one which doesn&#8217;t exhibit the problem doesn&#8217;t necessarily mean it&#8217;s not still a problem.</p>
</div></div>



<h3 class="wp-block-heading">A misunderstood workaround</h3>



<p>In parallel to all of the above discussion in the Swift Forums thread, I&#8217;d been diving into <code>wrk</code> to see what it was really doing. I discovered <em>a way</em> to eliminate the errors: by opening all the TCP connections in advance in a way that <em>happened</em> to limit how many were attempted concurrently by <code>wrk</code> thread count which <em>happened</em> to be low enough in my use of <code>wrk</code> to not hit the magic 128 limit (more on that later). As you can see in <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/123" data-wpel-link="external" target="_blank" rel="external noopener">my forum post on this</a>, I initially misunderstood how <code>wrk</code> functioned and misattributed the root cause as bugs / bad design in <code>wrk</code>.</p>



<p>In my defence, <code>wrk</code> isn&#8217;t written very well, eschewing such outrageous and bourgeois software engineering practices as, you know, actually checking for errors. So it wasn&#8217;t unreasonable to believe it was ultimately just broken, given plenty of evidence that it was at least partly broken (which it was &amp; is), but it was ultimately a mistake to let that cloud my judgement of each individual behaviour.</p>



<p>Then again, if I hadn&#8217;t been so appalled by the bad code in <code>wrk</code>, and taken it upon myself to rewrite key parts of it, I might not have stumbled onto the above &#8220;fix&#8221; and therefore also not found the true cause, later.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>✅ Improving error handling &amp; reporting is practically always a good idea. And when debugging a problem it can be helpful even if it doesn&#8217;t feel guided &#8211; the whole point of absent or incorrect error reporting is that you don&#8217;t know what you&#8217;re missing, so you may well reveal an important clue &#8220;by accident&#8221;.</p>
</div></div>



<h3 class="wp-block-heading">It&#8217;s never the compiler or the kernel… except when it is</h3>



<p>At the time I did think I&#8217;d actually <em>fixed</em> <code>wrk</code>; I didn&#8217;t realise I&#8217;d merely found an imperfect workaround. I&#8217;d solved the connection errors (not really)! But, I was still curious about one thing &#8211; something pretty much everyone had kinda ignored this whole time:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Though those lingering few read/write errors still bother me. I might look into them later.</p>
<cite>Me, <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/123" data-wpel-link="external" target="_blank" rel="external noopener">Swift Forums thread</a></cite></blockquote>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>✅ Curiosity is <em>powerful</em>. Why did my chocolate bar melt in my pocket when I walked through the lab, <a href="https://www.technologyreview.com/1999/01/01/236818/melted-chocolate-to-microwave/" data-wpel-link="external" target="_blank" rel="external noopener">maybe that&#8217;s interesting</a>? Why did this contaminated Petri dish end up full of fungus instead of bacteria, <a href="https://www.healio.com/news/endocrinology/20120325/penicillin-an-accidental-discovery-changed-the-course-of-medicine" data-wpel-link="external" target="_blank" rel="external noopener">maybe that&#8217;s interesting</a>? Why&#8217;s this unused screen glowing, <a href="https://www.aps.org/publications/apsnews/200111/history.cfm" data-wpel-link="external" target="_blank" rel="external noopener">maybe that&#8217;s interesting</a>? Ow, why did this apple fall on my head… but, <a href="https://education.nationalgeographic.org/resource/isaac-newton-who-he-was-why-apples-are-falling/" data-wpel-link="external" target="_blank" rel="external noopener">maybe that&#8217;s interesting</a>? (<a href="https://www.newscientist.com/article/2170052-newtons-apple-the-real-story/" data-wpel-link="external" target="_blank" rel="external noopener">apocryphal</a>, but close enough)</p>
</div></div>



<p>Tracing those reported errors to their cause was quite a challenge. The only known way to reproduce the errors was to use a very high number of concurrent TCP connections (several thousand), which made it hard to follow any <em>single</em> connection through its lifecycle using any low-brow methods (printf debugging etc). <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/129" data-wpel-link="external" target="_blank" rel="external noopener">I eventually managed</a> using System Trace<sup data-fn="65590619-a219-4fb0-87ea-fd2c98990365" class="fn"><a href="#65590619-a219-4fb0-87ea-fd2c98990365" id="65590619-a219-4fb0-87ea-fd2c98990365-link">2</a></sup> (lamenting, the entire time I used Instruments, that it would have been <a href="https://leopard-adc.pepas.com/documentation/DeveloperTools/Conceptual/SharkUserGuide/SystemTracing/SystemTracing.html" data-wpel-link="external" target="_blank" rel="external noopener">so much easier in Shark</a>).</p>



<p>Unfortunately, what I was seeing &#8211; while in fact correct &#8211; did not make sense to me, so I was hesitant to take it on face value.</p>



<p>The lack of any error reporting on the server side, because Vapor lacks it completely, was also both a known problem at the time and also a problem in hindsight. Had Vapor/NIO actually reported the errors they were encountering, it would have partially validated what I was seeing in the system traces &#8211; in fact, it would probably have saved me from having to capture &amp; analyse system traces.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>❌ Ignoring errors is always a bad idea. I mean, duh, right? But apparently it has to be reiterated.</p>
</div></div>



<p>Alas I don&#8217;t actually remember now precisely what led me to the final answers and root causes. I know it involved many hours of experimenting, exploring hypotheses, and in generally fiddling with everything I could think of.</p>



<p>Somehow or other, I did finally cotton on to a key configuration parameter: <code>kern.ipc.somaxconn</code>.</p>



<p>That controls how many connection requests can be pending (not formally accepted by the server) at one time. It defaults to 128 on macOS. Remember that number, 128?</p>



<p>Once I had figured out that <code>kern.ipc.somaxconn</code> directly controlled the problematic behaviour, the rest followed pretty naturally and quickly &#8211; I realised that what I saw in the system traces was in fact accurate, and that in turn revealed that the macOS kernel contains multiple surprisingly blatant and serious bugs (or at the very least dubious design choices, and lying documentation) regarding TCP sockets in non-blocking mode. I wrote that up in some detail in the second half of <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/132" data-wpel-link="external" target="_blank" rel="external noopener">this Swift Forums post</a>.</p>



<p>As a sidenote, that darn magic number that everyone kept ignoring &#8211; 128 &#8211; cropped up yet again, in <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/listen.2.html" data-wpel-link="external" target="_blank" rel="external noopener">the <code>listen</code> man page</a>, though by the time I saw it there it was merely a confirmation of what I&#8217;d already discovered, than a helpful clue. Still, perhaps there&#8217;s a lesson there: read the man page. 😆</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>❌ When documenting known bugs and limitations, explain them fully. Don&#8217;t just say e.g. &#8220;more than 128 doesn&#8217;t work&#8221;, say <em>why</em>.</p>
</div></div>



<h2 class="wp-block-heading">Conclusion</h2>



<p>All told, the major problems identified by the benchmark were (and not all of these were mentioned above, but you can find all the details in <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583" data-wpel-link="external" target="_blank" rel="external noopener">the Swift Forums thread</a>):</p>



<ul class="wp-block-list">
<li>The particular 3rd party library used for BigInt support in Swift, <a href="https://github.com/attaswift/BigInt" data-wpel-link="external" target="_blank" rel="external noopener">attaswift/BigInt</a>, performs quite poorly.</li>



<li>Vapor would accept too few connections per cycle of its event loop (promptly fixed, in <a href="https://github.com/vapor/vapor/releases/tag/4.96.0" data-wpel-link="external" target="_blank" rel="external noopener">4.96.0</a>).</li>



<li>The benchmark tool used, <code><a href="https://github.com/wg/wrk" data-wpel-link="external" target="_blank" rel="external noopener">wrk</a></code>, has numerous bugs:
<ul class="wp-block-list">
<li>It doesn&#8217;t always use the configured number of concurrent connections.</li>



<li>It doesn&#8217;t measure latency correctly.</li>



<li>It doesn&#8217;t report errors correctly (in the sense both that it miscategorises them, e.g. connect vs read/write, and that it doesn&#8217;t provide enough detail to understand what they are, such as by including the errno).</li>
</ul>
</li>



<li>The macOS kernel (and seemingly Linux kernel likewise) has multiple bugs:
<ul class="wp-block-list">
<li>Connection errors are reported incorrectly (as <code>ECONNRESET</code> or <code>EBADF</code>, instead of <code>ECONNREFUSED</code>).</li>



<li>kqueue (kevents) behaves as if all connections are always accepted, even when they are not. Put another way, you cannot actually tell if a connection was successful when using non-blocking sockets on macOS.</li>
</ul>
</li>



<li>Key network configuration on macOS &amp; Linux is way too restrictive:
<ul class="wp-block-list">
<li>Maximum file descriptors per process is only 2,560 generally on macOS, and even less (256) in GUI apps. It may vary on Linux, but on Axel&#8217;s particular server it was 1,024.</li>



<li>Maximum number of unaccepted connection requests (the <code>kern.ipc.somaxconn</code> sysctl on macOS, <code>/proc/sys/net/core/somaxconn</code> on Linux) is only 128 on macOS. It may vary on Linux.</li>
</ul>
</li>
</ul>



<p>It <em>appears</em> that the kernel bugs apply to Linux as well (although it&#8217;s not known if kqueue was in use there, as <code>wrk</code> also supports <code>epoll</code> and <code>select</code>), as the behaviour seems to be the same between macOS and Linux.</p>



<p>With the above issues fixed or worked around, <a href="https://tech.phlux.us/Juice-Sucking-Servers-Part-Trois/" data-wpel-link="external" target="_blank" rel="external noopener">his benchmark produces more explicable results</a> (but keep in mind that the difference in connection acceptance behaviour <em>is real</em> and reflects a different design trade-off in Vapor, which <em>may</em> be a problem for real-world use if you don&#8217;t raise <code>somaxconn</code> and the listen backlog limit enough).</p>



<p>And that&#8217;s all just the <em>problems</em> Axel&#8217;s benchmark surfaced &#8211; there was a whole host of other interesting lessons taken away from all this (only a fraction of which were highlighted in this post &#8211; many more can be found in Axel&#8217;s posts and the Swift Forums thread).</p>



<p>Nominally the end result is also a benchmark that shows Vapor (Swift) out-performing other popular web frameworks in other languages. <em>Hugely</em> out-performing them, if you factor in not just throughput &amp; latency but RAM &amp; power usage. But, to reiterate <a href="#do-these-improvements-apply-to-the-other-cases-too">what I pointed out earlier</a>, take that with a grain of salt.</p>



<p>So, for a benchmark that many initially decried as unrealistic or plain poorly conceived, it turned out to be pretty darn useful, I think. And if that doesn&#8217;t make it a successful benchmark, I don&#8217;t know what does.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h1 class="wp-block-heading">Addendum: post title</h1>



<p>Looking at the <a href="https://news.ycombinator.com/item?id=40374946" data-wpel-link="external" target="_blank" rel="external noopener">comments about this post on HackerNews</a> etc, I feel like I have to explain the title a little. I was quite pleased with myself when I came up with it (admittedly by accident), because it&#8217;s subtle and I think kinda clever, but perhaps too subtle.</p>



<p>&#8220;Swift sucks at web serving… or does it?&#8221; is a [platonic] double entendre.</p>



<p>On face value it&#8217;s alluding to the more typical type of post that is both (a) click-baity and (b) a standard &#8220;turns out&#8221; story where actually Swift is <em>awesome</em> at web server and haha to all those who doubted it. (where one can replace the word &#8220;Swift&#8221; with basically any programming technology, because benchmarking brings out some ugly competitiveness from the community)</p>



<p>But <em>really</em> what it means here, if you read the whole post, is that actually <em>we still don&#8217;t know</em>. It&#8217;s alluding to the oft-overlooked fact that benchmarks are rarely as conclusive as they&#8217;re presented. Which I thought was quite clever because it reiterates, at a meta level, my whole point about learning being more important than competing.</p>



<p>At least, that was the idea. 😆</p>


<ol class="wp-block-footnotes"><li id="e7728698-06db-400a-a6b9-01a2ce4f3e5b"><a href="https://github.com/helidon-io/helidon" data-wpel-link="external" target="_blank" rel="external noopener">Helidon itself is written in Java</a>, but <a href="https://gitlab.com/axello/serverbench/-/tree/main/java/src/main/kotlin?ref_type=heads" data-wpel-link="external" target="_blank" rel="external noopener">Axel used Kotlin for his little web server implementation</a> &#8211; including most crucially the Fibonacci calculations.  Both interoperate atop the <a href="https://en.wikipedia.org/wiki/Java_virtual_machine" data-wpel-link="external" target="_blank" rel="external noopener">JVM</a> and plenty of &#8220;Java&#8221; libraries are partly written in Kotlin, or have dependencies written in Kotlin &#8211; and vice versa.  A little like Objective-C and Swift interoperate such that many Mac / iDevice apps use a rich mix of both and you don&#8217;t typically need to care which language is used for any particular piece. <a href="#e7728698-06db-400a-a6b9-01a2ce4f3e5b-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="65590619-a219-4fb0-87ea-fd2c98990365">I always endeavour to link to the things I mention, but in this case there&#8217;s nothing to link to &#8211; Apple don&#8217;t provide any actual documentation of the System Trace tool in Instruments, and there&#8217;s not even any usable 3rd party guide to it, that I can find.  It&#8217;s a sad demonstration of Apple&#8217;s general indifference to performance tools. 😔<br><br>Apple don&#8217;t even have a proper product page for Instruments itself &#8211; the closest you can find is merely <a href="https://help.apple.com/instruments/mac/10.0/#/" data-wpel-link="external" target="_blank" rel="external noopener">its Help</a>. <a href="#65590619-a219-4fb0-87ea-fd2c98990365-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swift-sucks-at-web-serving-or-does-it/feed/</wfw:commentRss>
			<slash:comments>13</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/05/Web-server-comparison-throughput.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8061</post-id>	</item>
		<item>
		<title>Swift tip: the swap function</title>
		<link>https://wadetregaskis.com/swift-tip-the-swap-function/</link>
					<comments>https://wadetregaskis.com/swift-tip-the-swap-function/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 14 May 2024 23:43:20 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Education]]></category>
		<category><![CDATA[Fibonacci]]></category>
		<category><![CDATA[MutableCollection]]></category>
		<category><![CDATA[swap]]></category>
		<category><![CDATA[swapAt]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8079</guid>

					<description><![CDATA[The following code prints the Fibonacci sequence. You&#8217;ve probably seen it before. It&#8217;s one of the simplest and most well-known examples of a sliding window operation &#8211; where the next value depends on the preceding two (or more) values. While almost all programs do not calculate the Fibonacci sequence, many do contain similar sliding-window algorithms.&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swift-tip-the-swap-function/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>The following code prints the Fibonacci sequence. You&#8217;ve probably seen it before. It&#8217;s one of the simplest and most well-known examples of a sliding window operation &#8211; where the next value depends on the preceding two (or more) values. While almost all programs do <em>not</em> calculate the Fibonacci sequence, many do contain similar sliding-window algorithms. And code that uses the <code><a href="https://developer.apple.com/documentation/swift/array/reduce(_:_:)" data-wpel-link="external" target="_blank" rel="external noopener">reduce(_:_:)</a></code> / <code><a href="https://developer.apple.com/documentation/swift/array/reduce(into:_:)" data-wpel-link="external" target="_blank" rel="external noopener">reduce(into:_:)</a></code> methods is usually doing a similar thing, too.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> previous = </span><span style="color: #795E26">UIntXL</span><span style="color: #000000">(</span><span style="color: #098658">0</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> current = </span><span style="color: #795E26">UIntXL</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(previous)</span></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(current)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">while</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> next = previous + current</span></span>
<span class="line"><span style="color: #000000">    previous = current</span></span>
<span class="line"><span style="color: #000000">    current = next</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(next)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Short of using a different algorithm entirely (there are <a href="https://r-knott.surrey.ac.uk/Fibonacci/fibFormula.html" data-wpel-link="external" target="_blank" rel="external noopener">much smarter ways to calculate Fibonacci numbers</a>), you&#8217;d think it&#8217;d be optimally fast &#8211; I mean, how can the above example get any better, really?</p>



<p>Well, by doing this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> previous = </span><span style="color: #795E26">UIntXL</span><span style="color: #000000">(</span><span style="color: #098658">0</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> current = </span><span style="color: #795E26">UIntXL</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(previous)</span></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(current)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">while</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    previous += current</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">swap</span><span style="color: #000000">(&amp;previous, &amp;current)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(next)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Sometimes this version is significantly faster (e.g. <a href="https://forums.swift.org/t/standard-vapor-website-drops-1-5-of-requests-even-at-concurrency-of-100/71583/73" data-wpel-link="external" target="_blank" rel="external noopener">30% in a recent example</a>, and I&#8217;ve seen up to 14x in some of my own, similar cases).</p>



<p>Admittedly, sometimes it&#8217;s merely as fast &#8211; sometimes the Swift compiler optimises the first version into the second for us. But the compiler&#8217;s optimiser is unreliable and unpredictable. So if performance is important to you, it&#8217;s best to use <code>swap</code> explicitly rather than hang your hopes on the compiler.</p>



<h2 class="wp-block-heading">What does <code><a href="https://developer.apple.com/documentation/swift/swap(_:_:)" data-wpel-link="external" target="_blank" rel="external noopener">swap</a></code> do?</h2>



<p>Its purpose is pretty obvious &#8211; it swaps the contents of two variables. <em>Logically</em> it&#8217;s equivalent to<sup data-fn="3245c9e9-e853-452f-9f59-1821ed0d19bc" class="fn"><a href="#3245c9e9-e853-452f-9f59-1821ed0d19bc" id="3245c9e9-e853-452f-9f59-1821ed0d19bc-link">1</a></sup>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="func swap(_ a: inout T, _ b: inout T) {
    let tmp = a
    a = b
    b = tmp
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">swap</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">a</span><span style="color: #000000">: </span><span style="color: #0000FF">inout</span><span style="color: #000000"> T, </span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">b</span><span style="color: #000000">: </span><span style="color: #0000FF">inout</span><span style="color: #000000"> T) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> tmp = a</span></span>
<span class="line"><span style="color: #000000">    a = b</span></span>
<span class="line"><span style="color: #000000">    b = tmp</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>But that&#8217;s <em>not</em> how it&#8217;s actually implemented. The implementation starts <a href="https://github.com/apple/swift/blob/c1597154a935b0f61646c5accb79a45ce470f944/stdlib/public/core/MutableCollection.swift#L531" data-wpel-link="external" target="_blank" rel="external noopener">here</a>, though the pertinent details are hidden behind compiler built-ins. Suffice to know that it boils down to <em>simply swapping the raw bytes</em>.</p>



<h2 class="wp-block-heading">Why is that better?</h2>



<p>It avoids unnecessary work: temporary object initialisation and deinitialisation, memory allocation (and frees), reference-counting, etc.  That can be from the more efficient swap itself, and/or from mutating one of the values in place rather than creating a temporary third value.</p>



<p>It can also prevent unnecessary copy-on-write behaviour, if you&#8217;re mixing the swap in amongst other mutations on value types that implement reference semantics<sup data-fn="c5986145-3ce7-4086-ade3-ef670ccba9d1" class="fn"><a href="#c5986145-3ce7-4086-ade3-ef670ccba9d1" id="c5986145-3ce7-4086-ade3-ef670ccba9d1-link">2</a></sup> (like the standard <code>Array</code>, <code>Set</code>, etc).  That usually saves time but can also, in some relatively rare circumstances, save memory too (by not leaving duplicate copies of internal buffers in memory indefinitely).</p>



<p>It also works for <code><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0390-noncopyable-structs-and-enums.md" data-wpel-link="external" target="_blank" rel="external noopener">~Copyable</a></code> types without any extra effort.</p>



<h2 class="wp-block-heading">Is it always optimal?</h2>



<p>For <em>actually</em> swapping two values in RAM, <s>yes</s>.</p>



<p>Well, maybe.  Mostly?  It turns out &#8211; after I initially published this post &#8211; that it&#8217;s a bit more complicated than that.  <em>Sometimes</em> an alternative method is slightly faster:  the &#8220;tuple swap&#8221;.  e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="var previous = UIntXL(0)
var current = UIntXL(1)

print(previous)
print(current)

while true {
    previous += current
    (previous, current) = (current, previous)

    print(next)
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> previous = </span><span style="color: #795E26">UIntXL</span><span style="color: #000000">(</span><span style="color: #098658">0</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> current = </span><span style="color: #795E26">UIntXL</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(previous)</span></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(current)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">while</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    previous += current</span></span>
<span class="line"><span style="color: #000000">    (previous, current) = (current, previous)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(next)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>In <em>some</em> benchmarks that&#8217;s faster, in some it&#8217;s slower<sup data-fn="f4e9238d-e4d4-416f-90d2-369fd82972bb" class="fn"><a href="#f4e9238d-e4d4-416f-90d2-369fd82972bb" id="f4e9238d-e4d4-416f-90d2-369fd82972bb-link">3</a></sup>. It <em>seems</em> to always be faster than using a named temporary, just like <code>swap</code>, although I don&#8217;t believe that&#8217;s guaranteed behaviour. Furthermore, it relies on the compiler successfully recognising that particular expression of the swap intent and optimising it appropriately. Plus, using <code>swap</code> is shorter, clearer, and less error-prone. So I think an explicit call to <code>swap</code> is still the best option.</p>



<p>In any case, there are sometimes faster methods which eliminate the need to actually swap the bytes around. e.g. using a pointer to toggle between the two values. To my knowledge, Swift doesn&#8217;t provide any way to actually do that for value types, since Swift tries to pretend that pointers don&#8217;t exist for them. 😔</p>



<p>Hypothetically the optimiser could perform that optimisation for any code like the above examples, and others, although I&#8217;ve never seen it do that.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ <code>swap</code> has a cousin, <code><a href="https://developer.apple.com/documentation/swift/mutablecollection/swapat(_:_:)-8f65z" data-wpel-link="external" target="_blank" rel="external noopener">swapAt</a></code>, on <code><a href="https://developer.apple.com/documentation/swift/mutablecollection" data-wpel-link="external" target="_blank" rel="external noopener">MutableCollection</a></code>s. It&#8217;s even <em>less</em> known than <code>swap</code>, but it&#8217;s potentially even more frequently useful in general code. Its purpose is to likewise ensure that swapping two elements within the same collection is as efficient as possible, irrespective of how the compiler is feeling that day.</p>



<p>But, bizarrely, <a href="https://github.com/apple/swift/blob/c1597154a935b0f61646c5accb79a45ce470f944/stdlib/public/core/MutableCollection.swift#L320" data-wpel-link="external" target="_blank" rel="external noopener">its implementation</a> does <em>not</em> use <code>swap</code> or otherwise contain the same guaranteed optimisations. So it might <em>not</em> be the fastest way to swap two elements. 😕</p>



<p>It might be an unfortunate consequence of Swift&#8217;s exclusive access checking &#8211; specifically its lack of support for non-overlapping access within a collection &#8211; because if you do try to use <code>swap</code> yourself on the elements of a collection, the compiler refuses it:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><code>❌ Overlapping accesses to 'x', but modification requires exclusive access; consider calling MutableCollection.swapAt(_:_:)</code></p>
</blockquote>
</div></div>


<ol class="wp-block-footnotes"><li id="3245c9e9-e853-452f-9f59-1821ed0d19bc">Ignoring support for <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0390-noncopyable-structs-and-enums.md" data-wpel-link="external" target="_blank" rel="external noopener">~Copyable</a> types, which can be done but requires a more complicated implementation than we need to worry about here. <a href="#3245c9e9-e853-452f-9f59-1821ed0d19bc-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="c5986145-3ce7-4086-ade3-ef670ccba9d1">Many value types in Swift are <em>not</em> actually just simple sequences of bytes.  They often contain pointers to potentially-shared <em>reference</em> objects (e.g. classes or actors), or raw memory buffers.  When you copy the value &#8211; which includes simply assigning it to a new variable &#8211; there can be a lot of work involved in incrementing reference counts for these now-shared internal objects &amp; buffers (which also means time spent decrementing those counts some time later).<br><br>And that&#8217;s assuming a type that uses copy-on-write (or similar) optimisations &#8211; otherwise, you&#8217;re going to have to copy all those internal memory buffers as well, which can be tremendously expensive.<br><br>Even mere reference counting operations are actually pretty expensive &#8211; tens to hundreds of instructions each, typically.  <em>Usually</em> in Swift we don&#8217;t notice them because the optimiser works very hard to actually remove them, as much as possible. <a href="#c5986145-3ce7-4086-ade3-ef670ccba9d1-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="f4e9238d-e4d4-416f-90d2-369fd82972bb">I put together <a href="https://github.com/wadetregaskis/Swift-Benchmarks/blob/main/Benchmarks/Swap/Swap.swift" data-wpel-link="external" target="_blank" rel="external noopener">some benchmarks</a> in researching &amp; writing this post, with the intent of using them as specific examples of the performance differences.  Unfortunately they are apparently too simple, and the optimiser is generally able to optimise the named temporary method into using <code>swap</code> or the &#8220;tuple swap&#8221; method (even though in real-world code it usually fails to do so, in my experience). <a href="#f4e9238d-e4d4-416f-90d2-369fd82972bb-link" aria-label="Jump to footnote reference 3">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swift-tip-the-swap-function/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8079</post-id>	</item>
		<item>
		<title>URLSession performance for reading a byte stream</title>
		<link>https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/</link>
					<comments>https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 03 May 2024 23:52:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Benchmarked]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[NSData]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[URLSession]]></category>
		<category><![CDATA[withUnsafeBytes]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8006</guid>

					<description><![CDATA[What&#8217;s the best way to read a stream of bytes with URLSession? That&#8217;s the simple question I set out to answer. I wrote some benchmarks. They read a 128 MiB file and perform a contrived aggregation of its content bytes (a joking &#8220;hash&#8221; of them, merely to ensure the actual reads aren&#8217;t optimised out). ⚠️&#8230; <a class="read-more-link" href="https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>What&#8217;s the best way to read a stream of bytes with <code><a href="https://developer.apple.com/documentation/foundation/urlsession" data-wpel-link="external" target="_blank" rel="external noopener">URLSession</a></code>? That&#8217;s the simple question I set out to answer. I wrote <a href="https://github.com/wadetregaskis/Swift-Benchmarks/blob/main/Benchmarks/URLSession/URLSession.swift" data-wpel-link="external" target="_blank" rel="external noopener">some benchmarks</a>. They read a 128 MiB file and perform a contrived aggregation of its content bytes (a joking &#8220;hash&#8221; of them, merely to ensure the actual reads aren&#8217;t optimised out).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ In a nutshell, the results here demonstrate the <em>best-case</em> performance for each of the methods evaluated.  These benchmarks are very simple, which makes them relatively easy for the Swift compiler to optimise well.  In less trivial, real-world code, the optimiser might not do so great.  So these benchmarks and their results are merely one collective data point in the bigger picture of just how the heck to read files efficiently.  </p>
</div></div>



<p>There&#8217;s two key decisions you must make:  which specific <code>URLSession</code> API will you use, and how will you access the bytes themselves.</p>



<h2 class="wp-block-heading">Measurements</h2>



<p>Each benchmark was run a hundred times or for 30 seconds (whichever limit was hit first).  I&#8217;m highlighting here just the medians (in general there wasn&#8217;t much variation anyway), but you can dig into the other percentiles &amp; metrics via the disclosure triangles, if you like.</p>



<p>I&#8217;m pretty sure the reads were all served out of the kernel&#8217;s in-memory file system cache, judging by the lack of SSD read I/O reported by <a href="https://bjango.com/mac/istatmenus/" data-wpel-link="external" target="_blank" rel="external noopener">iStat Menus</a>.  But I didn&#8217;t go out of my way to verify this.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ &#8220;Peak RAM&#8221; is as reported by the <a href="https://github.com/ordo-one/package-benchmark" data-wpel-link="external" target="_blank" rel="external noopener">Benchmark</a> package, based on (if I understand it correctly) periodic sampling of the process RSS.  As such it&#8217;s not necessarily completely accurate, due to the potential to miss brief peaks.</p>
</div></div>



<h3 class="wp-block-heading">M2 MacBook Air</h3>



<figure class="wp-block-table aligncenter"><table><thead><tr><th>Method</th><th class="has-text-align-right" data-align="right">Wall time (ms)</th><th class="has-text-align-right" data-align="right">CPU time (ms)</th><th class="has-text-align-right" data-align="right">Throughput (MiB/s)</th><th class="has-text-align-right" data-align="right">Peak RAM (MB)</th></tr></thead><tbody><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767351-bytes" data-wpel-link="external" target="_blank" rel="external noopener">bytes(from:)</a></code> and for loop</td><td class="has-text-align-right" data-align="right">79</td><td class="has-text-align-right" data-align="right">138</td><td class="has-text-align-right" data-align="right">1,620</td><td class="has-text-align-right" data-align="right">91</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767351-bytes" data-wpel-link="external" target="_blank" rel="external noopener">bytes(from:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/urlsession/asyncbytes/3767347-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">79</td><td class="has-text-align-right" data-align="right">138</td><td class="has-text-align-right" data-align="right">1,620</td><td class="has-text-align-right" data-align="right">84</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and for loop</td><td class="has-text-align-right" data-align="right">605</td><td class="has-text-align-right" data-align="right">641</td><td class="has-text-align-right" data-align="right">212</td><td class="has-text-align-right" data-align="right">265</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and for loop inside <code><a href="https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeBytes</a></code></td><td class="has-text-align-right" data-align="right">60</td><td class="has-text-align-right" data-align="right">95</td><td class="has-text-align-right" data-align="right">2,133</td><td class="has-text-align-right" data-align="right">338</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/1780184-foreach" data-wpel-link="external" target="_blank" rel="external noopener">forEach</a></code></td><td class="has-text-align-right" data-align="right">765</td><td class="has-text-align-right" data-align="right">800</td><td class="has-text-align-right" data-align="right">167</td><td class="has-text-align-right" data-align="right">262</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/3126633-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">750</td><td class="has-text-align-right" data-align="right">784</td><td class="has-text-align-right" data-align="right">171</td><td class="has-text-align-right" data-align="right">290</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with for loop</td><td class="has-text-align-right" data-align="right">560</td><td class="has-text-align-right" data-align="right">617</td><td class="has-text-align-right" data-align="right">229</td><td class="has-text-align-right" data-align="right">53</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with for loop inside <code><a href="https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeBytes</a></code></td><td class="has-text-align-right" data-align="right">36</td><td class="has-text-align-right" data-align="right">75</td><td class="has-text-align-right" data-align="right">3,556</td><td class="has-text-align-right" data-align="right">26</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with <code><a href="https://developer.apple.com/documentation/foundation/data/1780184-foreach" data-wpel-link="external" target="_blank" rel="external noopener">forEach</a></code></td><td class="has-text-align-right" data-align="right">719</td><td class="has-text-align-right" data-align="right">775</td><td class="has-text-align-right" data-align="right">178</td><td class="has-text-align-right" data-align="right">51</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with <code><a href="https://developer.apple.com/documentation/foundation/data/3126633-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">709</td><td class="has-text-align-right" data-align="right">765</td><td class="has-text-align-right" data-align="right">167</td><td class="has-text-align-right" data-align="right">45</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and for loop&nbsp;</td><td class="has-text-align-right" data-align="right">590</td><td class="has-text-align-right" data-align="right">630</td><td class="has-text-align-right" data-align="right">217</td><td class="has-text-align-right" data-align="right">442</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and for loop inside <code><a href="https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeBytes</a></code></td><td class="has-text-align-right" data-align="right">57</td><td class="has-text-align-right" data-align="right">98</td><td class="has-text-align-right" data-align="right">2,246</td><td class="has-text-align-right" data-align="right">504</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/1780184-foreach" data-wpel-link="external" target="_blank" rel="external noopener">forEach</a></code></td><td class="has-text-align-right" data-align="right">742</td><td class="has-text-align-right" data-align="right">783</td><td class="has-text-align-right" data-align="right">173</td><td class="has-text-align-right" data-align="right">470</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/3126633-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">742</td><td class="has-text-align-right" data-align="right">786</td><td class="has-text-align-right" data-align="right">173</td><td class="has-text-align-right" data-align="right">485</td></tr></tbody></table></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Full results (raw text)</summary>
<pre class="wp-block-preformatted">bytewise read using bytes(from:) and for loop<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      35 │      36 │      37 │      37 │      37 │      67 │      69 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      68 │      91 │      91 │      91 │      94 │      94 │      94 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │    1841 │    1869 │    1883 │    1911 │    1925 │    1939 │    1953 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │    1312 │    1332 │    1342 │    1362 │    1372 │    1382 │    1392 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      24 │      25 │      25 │      25 │      25 │      25 │      25 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     136 │     137 │     138 │     138 │     138 │     139 │     140 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │      79 │      79 │      79 │      79 │      80 │      81 │      81 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using bytes(from:) and reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      35 │      36 │      37 │      37 │      37 │      67 │      69 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      68 │      83 │      84 │      85 │      88 │      88 │      88 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │    1840 │    1869 │    1897 │    1911 │    1925 │    1953 │    1967 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │    1312 │    1332 │    1352 │    1362 │    1372 │    1392 │    1402 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      24 │      25 │      25 │      25 │      25 │      25 │      25 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     137 │     138 │     138 │     138 │     138 │     139 │     140 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │      79 │      79 │      79 │      79 │      80 │      80 │      81 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and for loop<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      40 │      43 │      44 │      45 │      47 │      76 │      76 │      50 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     167 │     230 │     265 │     278 │     286 │     297 │     297 │      50 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      50 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      50 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      21 │      23 │      23 │      23 │      23 │      23 │      23 │      50 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     631 │     639 │     641 │     647 │     659 │     676 │     676 │      50 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     600 │     603 │     605 │     610 │     625 │     642 │     642 │      50 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and for loop inside withUnsafeBytes<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      39 │      44 │      44 │      45 │      47 │      75 │      76 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     150 │     293 │     338 │     398 │     419 │     439 │     445 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │      11 │      11 │      11 │      11 │      11 │      11 │      11 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │       5 │       5 │       5 │       5 │       5 │       5 │       5 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      21 │      22 │      22 │      23 │      23 │      23 │      23 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │      79 │      93 │      95 │      96 │      98 │     106 │     110 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │      49 │      59 │      60 │      61 │      62 │      68 │      71 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and forEach<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      41 │      43 │      44 │      45 │      47 │      77 │      77 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     145 │     228 │     262 │     279 │     292 │     296 │     296 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      22 │      22 │      23 │      23 │      23 │      23 │      23 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     782 │     794 │     800 │     805 │     806 │     816 │     816 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     750 │     760 │     765 │     771 │     773 │     783 │     783 │      40 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      41 │      44 │      44 │      45 │      47 │      77 │      77 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     165 │     254 │     290 │     338 │     353 │     361 │     361 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      22 │      22 │      22 │      23 │      23 │      23 │      23 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     771 │     777 │     784 │     789 │     792 │     796 │     796 │      40 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     738 │     742 │     750 │     756 │     758 │     760 │     760 │      40 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with for loop<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      34 │      35 │      36 │      36 │      37 │      69 │      69 │      54 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      52 │      52 │      53 │      57 │      57 │      57 │      57 │      54 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      54 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      54 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      26 │      26 │      27 │      27 │      27 │      27 │      27 │      54 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     616 │     616 │     617 │     617 │     617 │     620 │     620 │      54 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     559 │     559 │     560 │     560 │     560 │     564 │     564 │      54 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with for loop inside withUnsafeBytes<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      47 │      51 │      53 │      55 │      57 │      87 │      87 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      22 │      26 │      26 │      27 │      27 │      27 │      27 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │    6135 │    6147 │    6147 │    6147 │    6150 │    6150 │    6150 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │    2046 │    2051 │    2051 │    2051 │    2051 │    2051 │    2051 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      35 │      35 │      35 │      35 │      35 │      35 │      35 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │      73 │      74 │      75 │      75 │      76 │      76 │      76 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │      35 │      36 │      36 │      36 │      36 │      37 │      37 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with forEach<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      34 │      35 │      36 │      36 │      37 │      68 │      68 │      42 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      47 │      50 │      51 │      51 │      51 │      51 │      51 │      42 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      42 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      42 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      26 │      27 │      27 │      27 │      27 │      27 │      27 │      42 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     773 │     774 │     775 │     775 │     776 │     779 │     779 │      42 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     717 │     718 │     719 │     719 │     719 │     722 │     722 │      42 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      34 │      35 │      36 │      36 │      37 │      70 │      70 │      43 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      45 │      45 │      45 │      45 │      45 │      45 │      45 │      43 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      43 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      43 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      26 │      26 │      27 │      27 │      27 │      27 │      27 │      43 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     763 │     764 │     765 │     766 │     766 │     768 │     768 │      43 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     707 │     709 │     709 │     710 │     710 │     714 │     714 │      43 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and for loop <br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      39 │      43 │      44 │      47 │      50 │      79 │      79 │      51 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     378 │     410 │     442 │     471 │     496 │     528 │     528 │      51 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      51 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      51 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      27 │      27 │      28 │      28 │      28 │      28 │      28 │      51 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     624 │     627 │     630 │     632 │     634 │     640 │     640 │      51 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     587 │     589 │     590 │     594 │     597 │     601 │     601 │      51 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and for loop inside withUnsafeBytes<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      38 │      43 │      45 │      46 │      48 │      76 │      76 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     394 │     462 │     504 │     525 │     547 │     583 │     587 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │       5 │       5 │       5 │       5 │       5 │       5 │       5 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │       3 │       3 │       3 │       3 │       3 │       3 │       3 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      27 │      27 │      28 │      28 │      28 │      28 │      28 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │      92 │      96 │      98 │     100 │     102 │     105 │     106 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │      55 │      57 │      57 │      58 │      59 │      61 │      63 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and forEach<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      38 │      41 │      44 │      47 │      51 │      74 │      74 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     391 │     430 │     470 │     560 │     587 │     617 │     617 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      27 │      28 │      28 │      28 │      28 │      28 │      28 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     773 │     776 │     783 │     789 │     790 │     795 │     795 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     734 │     737 │     742 │     749 │     751 │     755 │     755 │      41 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      38 │      43 │      44 │      46 │      51 │      77 │      77 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     348 │     408 │     451 │     485 │     499 │     548 │     548 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      27 │      28 │      28 │      28 │      28 │      28 │      28 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     770 │     776 │     786 │     788 │     793 │     796 │     796 │      41 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     732 │     737 │     742 │     749 │     752 │     755 │     755 │      41 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛</pre>
</details>



<h3 class="wp-block-heading">10-core iMac Pro</h3>



<figure class="wp-block-table aligncenter"><table><thead><tr><th>Method</th><th class="has-text-align-right" data-align="right">Wall time (ms)</th><th class="has-text-align-right" data-align="right">CPU time (ms)</th><th class="has-text-align-right" data-align="right">Throughput (MiB/s)</th><th class="has-text-align-right" data-align="right">Peak RAM (MB)</th></tr></thead><tbody><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767351-bytes" data-wpel-link="external" target="_blank" rel="external noopener">bytes(from:)</a></code> and for loop</td><td class="has-text-align-right" data-align="right">138</td><td class="has-text-align-right" data-align="right">250</td><td class="has-text-align-right" data-align="right">928</td><td class="has-text-align-right" data-align="right">54</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767351-bytes" data-wpel-link="external" target="_blank" rel="external noopener">bytes(from:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/urlsession/asyncbytes/3767347-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">139</td><td class="has-text-align-right" data-align="right">251</td><td class="has-text-align-right" data-align="right">921</td><td class="has-text-align-right" data-align="right">53</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and for loop</td><td class="has-text-align-right" data-align="right">953</td><td class="has-text-align-right" data-align="right">1,023</td><td class="has-text-align-right" data-align="right">134</td><td class="has-text-align-right" data-align="right">257</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and for loop inside <code><a href="https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeBytes</a></code></td><td class="has-text-align-right" data-align="right">140</td><td class="has-text-align-right" data-align="right">208</td><td class="has-text-align-right" data-align="right">914</td><td class="has-text-align-right" data-align="right">344</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/1780184-foreach" data-wpel-link="external" target="_blank" rel="external noopener">forEach</a></code></td><td class="has-text-align-right" data-align="right">1,181</td><td class="has-text-align-right" data-align="right">1,254</td><td class="has-text-align-right" data-align="right">108</td><td class="has-text-align-right" data-align="right">236</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data" data-wpel-link="external" target="_blank" rel="external noopener">data(from:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/3126633-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">1,163</td><td class="has-text-align-right" data-align="right">1,233</td><td class="has-text-align-right" data-align="right">110</td><td class="has-text-align-right" data-align="right">232</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with for loop</td><td class="has-text-align-right" data-align="right">848</td><td class="has-text-align-right" data-align="right">964</td><td class="has-text-align-right" data-align="right">151</td><td class="has-text-align-right" data-align="right">40</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with for loop inside <code><a href="https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeBytes</a></code></td><td class="has-text-align-right" data-align="right">56</td><td class="has-text-align-right" data-align="right">140</td><td class="has-text-align-right" data-align="right">2,286</td><td class="has-text-align-right" data-align="right">23</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with <code><a href="https://developer.apple.com/documentation/foundation/data/1780184-foreach" data-wpel-link="external" target="_blank" rel="external noopener">forEach</a></code></td><td class="has-text-align-right" data-align="right">1,066</td><td class="has-text-align-right" data-align="right">1,181</td><td class="has-text-align-right" data-align="right">120</td><td class="has-text-align-right" data-align="right">35</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:)</a></code> and an incremental delegate with <code><a href="https://developer.apple.com/documentation/foundation/data/3126633-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">1,072</td><td class="has-text-align-right" data-align="right">1,185</td><td class="has-text-align-right" data-align="right">119</td><td class="has-text-align-right" data-align="right">43</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and for loop&nbsp;</td><td class="has-text-align-right" data-align="right">948</td><td class="has-text-align-right" data-align="right">1,026</td><td class="has-text-align-right" data-align="right">135</td><td class="has-text-align-right" data-align="right">375</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and for loop inside <code><a href="https://developer.apple.com/documentation/foundation/data/3139154-withunsafebytes" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeBytes</a></code></td><td class="has-text-align-right" data-align="right">137</td><td class="has-text-align-right" data-align="right">215</td><td class="has-text-align-right" data-align="right">934</td><td class="has-text-align-right" data-align="right">591</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/1780184-foreach" data-wpel-link="external" target="_blank" rel="external noopener">forEach</a></code></td><td class="has-text-align-right" data-align="right">1,179</td><td class="has-text-align-right" data-align="right">1,258</td><td class="has-text-align-right" data-align="right">109</td><td class="has-text-align-right" data-align="right">370</td></tr><tr><td><code><a href="https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask" data-wpel-link="external" target="_blank" rel="external noopener">dataTask(with:completionHandler:)</a></code> and <code><a href="https://developer.apple.com/documentation/foundation/data/3126633-reduce" data-wpel-link="external" target="_blank" rel="external noopener">reduce</a></code></td><td class="has-text-align-right" data-align="right">1,176</td><td class="has-text-align-right" data-align="right">1,254</td><td class="has-text-align-right" data-align="right">109</td><td class="has-text-align-right" data-align="right">416</td></tr></tbody></table></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Full results (raw text)</summary>
<pre class="wp-block-preformatted">bytewise read using bytes(from:) and for loop<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      36 │      37 │      37 │      37 │      37 │      68 │      69 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      45 │      51 │      54 │      56 │      62 │      66 │      66 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │    1952 │    2023 │    2051 │    2079 │    2093 │    2135 │    2135 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │    1392 │    1442 │    1462 │    1482 │    1492 │    1522 │    1522 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      21 │      21 │      21 │      21 │      21 │      21 │      21 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     245 │     248 │     250 │     251 │     254 │     259 │     267 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     136 │     138 │     138 │     140 │     141 │     145 │     146 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using bytes(from:) and reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      36 │      37 │      37 │      37 │      37 │      67 │      69 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      48 │      52 │      53 │      54 │      56 │      58 │      60 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │    1953 │    2009 │    2037 │    2065 │    2079 │    2121 │    2121 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │    1392 │    1432 │    1452 │    1472 │    1482 │    1512 │    1512 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      21 │      21 │      21 │      21 │      21 │      21 │      21 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     246 │     249 │     251 │     253 │     256 │     274 │     278 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     136 │     138 │     139 │     140 │     141 │     150 │     151 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and for loop<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      41 │      42 │      43 │      43 │      72 │      76 │      76 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     188 │     243 │     257 │     266 │     279 │     316 │     316 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      18 │      18 │      19 │      19 │      19 │      19 │      19 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1010 │    1015 │    1023 │    1031 │    1040 │    1060 │    1060 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     941 │     946 │     953 │     958 │     968 │     985 │     985 │      32 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and for loop inside withUnsafeBytes<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      38 │      42 │      43 │      44 │      47 │      72 │      73 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     244 │     328 │     344 │     352 │     358 │     369 │     370 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │      11 │      11 │      11 │      11 │      11 │      11 │      11 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │       5 │       5 │       5 │       5 │       5 │       5 │       5 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      18 │      18 │      18 │      18 │      18 │      19 │      19 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     203 │     207 │     208 │     210 │     213 │     221 │     234 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     135 │     139 │     140 │     141 │     143 │     148 │     159 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and forEach<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      41 │      42 │      43 │      43 │      73 │      75 │      75 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     171 │     225 │     236 │     263 │     283 │     288 │     288 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      18 │      18 │      18 │      19 │      19 │      19 │      19 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1247 │    1250 │    1254 │    1266 │    1271 │    1284 │    1284 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │    1177 │    1179 │    1181 │    1194 │    1199 │    1211 │    1211 │      26 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using data(from:) and reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      39 │      42 │      43 │      43 │      72 │      79 │      79 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     207 │     226 │     232 │     243 │     260 │     278 │     278 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      18 │      18 │      18 │      19 │      19 │      19 │      19 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1225 │    1228 │    1233 │    1243 │    1250 │    1269 │    1269 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │    1157 │    1160 │    1163 │    1172 │    1178 │    1198 │    1198 │      26 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with for loop<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      35 │      35 │      36 │      36 │      65 │      68 │      68 │      36 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      32 │      36 │      40 │      45 │      48 │      52 │      52 │      36 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      36 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      36 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      23 │      23 │      23 │      23 │      23 │      23 │      36 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     954 │     958 │     964 │     965 │     975 │     992 │     992 │      36 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     841 │     845 │     848 │     851 │     857 │     871 │     871 │      36 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with for loop inside withUnsafeBytes<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      47 │      51 │      53 │      54 │      56 │      83 │      84 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      17 │      20 │      23 │      24 │      25 │      25 │      25 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │    5895 │    6067 │    6087 │    6099 │    6111 │    6123 │    6123 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │    1966 │    2024 │    2031 │    2035 │    2039 │    2043 │    2043 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      32 │      32 │      32 │      32 │      33 │      33 │      33 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     137 │     139 │     140 │     142 │     147 │     152 │     155 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │      55 │      55 │      56 │      56 │      58 │      60 │      62 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with forEach<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      35 │      35 │      36 │      36 │      65 │      69 │      69 │      29 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      29 │      33 │      35 │      37 │      39 │      41 │      41 │      29 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      29 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      29 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      23 │      23 │      23 │      23 │      23 │      23 │      29 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1169 │    1179 │    1181 │    1184 │    1190 │    1205 │    1205 │      29 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │    1056 │    1065 │    1066 │    1069 │    1074 │    1086 │    1086 │      29 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:) and an incremental delegate with reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      35 │      35 │      35 │      36 │      65 │      68 │      68 │      28 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │      30 │      35 │      43 │      46 │      48 │      52 │      52 │      28 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      28 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      28 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      23 │      23 │      23 │      23 │      23 │      23 │      28 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1176 │    1182 │    1185 │    1190 │    1198 │    1200 │    1200 │      28 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │    1061 │    1067 │    1072 │    1075 │    1080 │    1083 │    1083 │      28 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and for loop <br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      40 │      42 │      43 │      43 │      72 │      75 │      75 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     356 │     368 │     375 │     386 │     400 │     407 │     407 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      24 │      24 │      24 │      24 │      24 │      24 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1016 │    1023 │    1026 │    1032 │    1041 │    1048 │    1048 │      32 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     940 │     946 │     948 │     952 │     961 │     967 │     967 │      32 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and for loop inside withUnsafeBytes<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      40 │      42 │      43 │      43 │      44 │      74 │      74 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     370 │     580 │     591 │     599 │     607 │     613 │     614 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases                   │       5 │       5 │       5 │       5 │       5 │       5 │       5 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains                    │       3 │       3 │       3 │       3 │       3 │       3 │       3 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      23 │      24 │      24 │      24 │      24 │      24 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │     210 │     213 │     215 │     217 │     220 │     236 │     238 │     100 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │     133 │     135 │     137 │     138 │     140 │     153 │     154 │     100 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and forEach<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      41 │      42 │      42 │      43 │      71 │      75 │      75 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     332 │     364 │     370 │     384 │     415 │     418 │     418 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      24 │      24 │      24 │      24 │      24 │      24 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1243 │    1253 │    1258 │    1268 │    1270 │    1277 │    1277 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │    1164 │    1175 │    1179 │    1188 │    1190 │    1201 │    1201 │      26 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛<br><br>bytewise read using dataTask(with:completionHandler:) and reduce<br>╒════════════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕<br>│ Metric                     │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │<br>╞════════════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡<br>│ Malloc (total) (K)         │      40 │      42 │      42 │      43 │      72 │      75 │      75 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Memory (resident peak) (M) │     353 │     392 │     416 │     452 │     472 │     473 │     473 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Releases (K)               │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Retains (K)                │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │    4194 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Syscalls (total) (K)       │      23 │      24 │      24 │      24 │      24 │      24 │      24 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (total CPU) (ms)      │    1241 │    1250 │    1254 │    1266 │    1278 │    1281 │    1281 │      26 │<br>├────────────────────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Time (wall clock) (ms)     │    1164 │    1172 │    1176 │    1185 │    1195 │    1200 │    1200 │      26 │<br>╘════════════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛</pre>
</details>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ I didn&#8217;t include it in these initial results, but there&#8217;s also <code><a href="https://developer.apple.com/documentation/foundation/data/1780329-enumeratebytes" data-wpel-link="external" target="_blank" rel="external noopener">enumerateBytes</a></code>, carried over from <code>NSData</code>.  It performs identically to <code>withUnsafeBytes</code>.  However, it is officially deprecated (Apple claims that for-loops are the replacement, even though they&#8217;re an order of magnitude slower 🤨).</p>



<p>🤔 I also tried to test the <code>NSData</code> <code><a href="https://developer.apple.com/documentation/foundation/nsdata/1410616-bytes" data-wpel-link="external" target="_blank" rel="external noopener">bytes</a></code> property, but no matter how it&#8217;s used, it always results in the benchmark crashing.  It seems like it is actually unusable from Swift due to a memory management bug in the bridging layer and/or Swift compiler…?</p>
</div></div>



<h2 class="wp-block-heading">Observations</h2>



<h3 class="wp-block-heading">Incremental reads + withUnsafeBytes is unequivocally the best method</h3>



<p>It&#8217;s dramatically faster than any other approach &#8211; both in wall time and overall CPU usage &#8211; <em>and</em> uses the least amount of memory by far.</p>



<p>Within the <code><a href="https://developer.apple.com/documentation/foundation/data" data-wpel-link="external" target="_blank" rel="external noopener">Data</a></code>-centric methods this doesn&#8217;t surprise me &#8211; with the incremental approach <code>URLSession</code> can just hand data back as it comes in, in whatever chunk sizes are most convenient.  In the other <code>Data</code>-based approaches it has to aggregate everything into one final contiguous blob.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>🤔 This is all assuming that <code>URLSession</code> never memory-maps files, which I did not actually verify but does seem to be the case based on the performance and behaviour.  This strikes me as very odd, however, because memory-mapping the files would very likely be significantly faster, in the cases where it has to provide the entire contents as a single <code>Data</code> instance.  And <code>Data</code> already supports memory-mapping a file, very easily.</p>



<p>Of course, if your use-case doesn&#8217;t involve local files, then memory-mapping probably doesn&#8217;t apply anyway (unless <code>URLSession</code> uses a disk cache and the file is already in the cache &#8211; but I don&#8217;t know if it supports that).</p>
</div></div>



<h4 class="wp-block-heading">Implementation note</h4>



<p>I utilised the incremental API by basically adapting it to invoke a closure (shown below), mainly because it made it easier to then test different byte enumeration approaches within it, but the results should hold for typical implementations of <code><a href="https://developer.apple.com/documentation/foundation/urlsessiondatadelegate" data-wpel-link="external" target="_blank" rel="external noopener">URLSessionDataDelegate</a></code>.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ This isn&#8217;t a robust implementation; it&#8217;s not suitable for use in a real program, merely sufficient for this very specific application in these benchmarks. It doesn&#8217;t communicate failures correctly, behaves very poorly if misused (e.g. by using it for more than one operation), naively blocks the thread that&#8217;s awaiting the data, etc. Please don&#8217;t use it as-is, but feel free to evolve it into a real solution for your own uses.</p>
</div></div>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class IncrementalDataDelegate: NSObject, URLSessionDataDelegate {
    private let task: URLSessionTask
    private let handler: (Data) -&gt; ()
    private let done = NSCondition()

    init(_ task: URLSessionTask,
         handler: @escaping (Data) -&gt; ()) {
        self.task = task
        self.handler = handler
        super.init()
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        precondition(self.task == dataTask)
        self.handler(data)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) {
        precondition(self.task == task)

        if let error {
            preconditionFailure(&quot;Error: \(error)&quot;)
        }

        self.done.broadcast()
    }

    func wait() {
        self.done.wait()
    }
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">IncrementalDataDelegate</span><span style="color: #000000">: NSObject, URLSessionDataDelegate {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> task: URLSessionTask</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> handler: (Data) -&gt; ()</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> done = </span><span style="color: #795E26">NSCondition</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">task</span><span style="color: #000000">: URLSessionTask,</span></span>
<span class="line"><span style="color: #000000">         </span><span style="color: #795E26">handler</span><span style="color: #000000">: </span><span style="color: #0000FF">@escaping</span><span style="color: #000000"> (Data) -&gt; ()) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">task</span><span style="color: #000000"> = task</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">handler</span><span style="color: #000000"> = handler</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">urlSession</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">session</span><span style="color: #000000">: URLSession, </span><span style="color: #795E26">dataTask</span><span style="color: #000000">: URLSessionDataTask, </span><span style="color: #795E26">didReceive</span><span style="color: #000000"> </span><span style="color: #001080">data</span><span style="color: #000000">: Data) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">precondition</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">task</span><span style="color: #000000"> == dataTask)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">handler</span><span style="color: #000000">(data)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">urlSession</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">session</span><span style="color: #000000">: URLSession, </span><span style="color: #795E26">task</span><span style="color: #000000">: URLSessionTask, </span><span style="color: #795E26">didCompleteWithError</span><span style="color: #000000"> </span><span style="color: #001080">error</span><span style="color: #000000">: (any </span><span style="color: #267F99">Error</span><span style="color: #000000">)?) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">precondition</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">task</span><span style="color: #000000"> == task)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> error {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">preconditionFailure</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Error: </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">error</span><span style="color: #0000FF">)</span><span style="color: #A31515">&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">done</span><span style="color: #000000">.</span><span style="color: #795E26">broadcast</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">wait</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">done</span><span style="color: #000000">.</span><span style="color: #795E26">wait</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h3 class="wp-block-heading"><a href="https://developer.apple.com/documentation/foundation/urlsession/asyncbytes" data-wpel-link="external" target="_blank" rel="external noopener">AsyncBytes</a> (<a href="https://developer.apple.com/documentation/foundation/urlsession/3767351-bytes" data-wpel-link="external" target="_blank" rel="external noopener">bytes(from:)</a>) is surprisingly not bad  &#8211; <em>in this specific case</em></h3>



<p>This was surprising to me because generally I&#8217;ve seen Swift&#8217;s <code><a href="https://developer.apple.com/documentation/swift/asyncsequence" data-wpel-link="external" target="_blank" rel="external noopener">AsyncSequence</a></code> stuff &#8211; especially for operating on individual bytes &#8211; being unusably slow and inefficient.  It&#8217;s actually what prompted me to do these benchmarks, because I stubbornly tried using <code>bytes(from:)</code> in a current project and the performance in that real app was god-awful.  These benchmarks demonstrate that it doesn&#8217;t necessarily <em>have</em> to be, and there&#8217;s something more complicated going on.  I&#8217;m yet to get to the bottom of that.</p>



<p>The problem seems to be that async code in general &#8211; but <em>especially</em> anything involving <code>AsyncSequence</code>s &#8211; is <em>terribly</em> dependent on the compiler&#8217;s optimiser.  If the optimiser does anything less than an astounding job on it, the performance can drop off a cliff.</p>



<p>So, while these results nominally recommend <code>bytes(from:)</code> as a decent way to use <code>URLSession</code> &#8211; being a respectable second-fastest in these benchmarks and noticeably easier to use than the fastest method &#8211; I&#8217;d be very cautious about it and test the performance early and often.</p>



<h3 class="wp-block-heading"><code>withUnsafeBytes</code> is <em>way</em> faster than &#8220;safe&#8221; access to <code>Data</code>&#8216;s contents</h3>



<p>It&#8217;s an order of magnitude faster an Apple Silicon, and &#8216;merely&#8217; seven to nine times faster on Intel.</p>



<p>This isn&#8217;t surprising &#8211; <code>Data</code>&#8216;s regular APIs involve actual <em>function calls</em> (if not also Objective-C message sends, depending on what exactly is being returned by <code>URLSession</code> (native <code>Data</code> or actual <code>NSData</code>) and how Swift imports <code>NSData</code> from Objective-C.  <code>withUnsafeBytes</code> provides basically direct memory access, with practically zero overhead.</p>



<p>However, it has one notable downside…</p>



<h3 class="wp-block-heading"><code>withUnsafeBytes</code> doubles the memory usage of the target <code>Data</code></h3>



<p>This surprised and disappointed me &#8211; <code>Data</code> is <em>supposed</em> to already be a contiguous array of bytes, internally, so accessing those bytes with <code>withUnsafeBytes</code> should be nothing more than returning a pointer to that internal storage.  But in at least some cases, it doesn&#8217;t &#8211; instead, it allocates a whole new memory allocation, copies its contents to that allocation, and then provides that instead (and releases it afterwards &#8211; so repeated calls will incur this overhead every time).</p>



<p>This isn&#8217;t a <em>huge</em> issue when the <code>Data</code>s in question are small &#8211; clearly it doesn&#8217;t hamper performance all that much, since it&#8217;s still the fastest way to access the bytes of even a 128 MiB <code>Data</code> &#8211; but it can be an issue when the <code>Data</code>s in question are not small.  If you run out of free RAM, the cost of the kernel&#8217;s in-memory compression or swapping is very likely going to cripple the performance far beyond the degrees seen here by using the slow APIs.</p>



<h3 class="wp-block-heading">Reading a file with URLSession takes more than one CPU core</h3>



<p>I pointedly included <em>both</em> wall time and CPU time to highlight that there&#8217;s multiple cores engaged simultaneously for a single read.  This isn&#8217;t surprising, but it&#8217;s important to remember if you&#8217;re doing lots of parallel I/O &#8211; you can&#8217;t just naively allocate one read operation per CPU core and expect linear scaling (notwithstanding CPU frequency scaling etc anyway).</p>



<p>Though, that&#8217;s generally true anyway because most systems don&#8217;t have enough disk or network I/O to keep up with the CPU anyway.</p>



<h3 class="wp-block-heading">for loops are faster than <code>forEach</code> &amp; <code>reduce</code></h3>



<p>This might surprise some folks.  It&#8217;s surely a bit of a sore point with functional programming dogmatists.  The difference isn&#8217;t <em>massive</em> &#8211; in these benchmarks it&#8217;s only about 20%.  Still, it&#8217;s measurable and noticeable.</p>



<p>I find the for-loop approach easier to write and read anyway, so IMO this is just another reason to favour that instead of functional programming styles.  But not a reason to unilaterally favour one over the other.</p>



<h3 class="wp-block-heading"><code>forEach</code> &amp; <code>reduce</code> perform the same</h3>



<p>Not surprising or news, but worth noting.  In principle the optimiser should reduce them to the exact same machine code in the end.</p>



<h3 class="wp-block-heading">Similar performance characteristics between [old] Intel Xeons and Apple Silicon</h3>



<p>The M2 was faster, of course, but maybe not <em>as much</em> faster than one would expect &#8211; at best only twice as fast, which (subjectively) feels underwhelming given Apple&#8217;s numerous manufacturing and design advantages versus my iMac Pro&#8217;s old Xeon.</p>



<p>What I mean, though, is that the relative performance of the different methods is about the same irrespective of the platform.  What&#8217;s good (or bad) on an M2 is likewise on an Intel CPU.  Which is worth appreciating &#8211; although x86 is rapidly fading into irrelevance, it&#8217;s not quite there yet, and it&#8217;s always unpleasant when optimising for one architecture has the opposite effect on another.</p>



<h3 class="wp-block-heading">You&#8217;re probably not going to be I/O limited on Apple SSDs</h3>



<p>(for a single read at a time, that is)</p>



<p>Even in the very best case shown here &#8211; and despite the <em>very</em> light workload these benchmarks impose, on the actual file data &#8211; the best throughput was merely ~3.6 GB/s.  That&#8217;s not bad, of course &#8211; only a few years ago that would have easily saturated any consumer storage device, even a big Thunderbolt RAID array of SSDs.  But these days most of Apple&#8217;s computers have PCIe 4, quad-lane SSDs that have read speeds of about 7 GB/s.</p>



<p><em>And</em>, this is all ignoring the fact that <em>actually</em> reading from an SSD has more overhead than merely reading from the kernel&#8217;s file system cache, as these benchmarks almost certainly did. So actual SSD read performance is very likely lower than what these benchmarks achieved.</p>



<p>That all said, it doesn&#8217;t necessarily take much to be I/O-limited &#8211; two or three concurrent reads, efficiently implemented, would probably do it.  Certainly if you just spin up an operation per CPU core and they all try to do I/O at once, even a rather inefficient implementation will hit the SSD&#8217;s limits.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8006</post-id>	</item>
		<item>
		<title>Swift&#8217;s native Clocks are very inefficient</title>
		<link>https://wadetregaskis.com/swifts-native-clocks-are-very-inefficient/</link>
					<comments>https://wadetregaskis.com/swifts-native-clocks-are-very-inefficient/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 03 May 2024 02:10:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Benchmarked]]></category>
		<category><![CDATA[clock_gettime_nsec_np]]></category>
		<category><![CDATA[ContinuousClock]]></category>
		<category><![CDATA[gettimeofday]]></category>
		<category><![CDATA[Inefficient by design]]></category>
		<category><![CDATA[mach_absolute_time]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SuspendingClock]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7990</guid>

					<description><![CDATA[By which I mean, things like ContinuousClock and SuspendingClock. In absolute terms they don&#8217;t have much overhead &#8211; think sub-microsecond for most uses. Which makes them perfectly acceptable when they&#8217;re used sporadically (e.g. only a few times per second). However, if you need to deal with time and timing more frequently, their inefficiency can become&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swifts-native-clocks-are-very-inefficient/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>By which I mean, things like <code><a href="https://developer.apple.com/documentation/swift/continuousclock" data-wpel-link="external" target="_blank" rel="external noopener">ContinuousClock</a></code> and <code><a href="https://developer.apple.com/documentation/swift/suspendingclock" data-wpel-link="external" target="_blank" rel="external noopener">SuspendingClock</a></code>.</p>



<p>In absolute terms they don&#8217;t have much overhead &#8211; think sub-microsecond for most uses. Which makes them perfectly acceptable when they&#8217;re used sporadically (e.g. only a few times per second).</p>



<p>However, if you need to deal with time and timing more frequently, their inefficiency can become a serious bottleneck.</p>



<p>I stumbled into this because of a fairly common and otherwise uninteresting pattern &#8211; throttling UI updates on an I/O operation&#8217;s progress. This might look something like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Example</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> bytes: AsyncSequence&lt;</span><span style="color: #267F99">UInt8</span><span style="color: #000000">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@State</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> byteCount = </span><span style="color: #098658">0</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some View {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">Text</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Bytes so far: </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">byteCount.</span><span style="color: #795E26">formatted</span><span style="color: #000000FF">(.</span><span style="color: #795E26">byteCount</span><span style="color: #000000FF">(</span><span style="color: #795E26">style</span><span style="color: #000000FF">: .</span><span style="color: #001080">binary</span><span style="color: #000000FF">))</span><span style="color: #0000FF">)</span><span style="color: #A31515">&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">            .</span><span style="color: #001080">task</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #0000FF">var</span><span style="color: #000000"> unpostedByteCount = </span><span style="color: #098658">0</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #0000FF">let</span><span style="color: #000000"> clock = </span><span style="color: #795E26">ContinuousClock</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #0000FF">var</span><span style="color: #000000"> lastUpdate = clock.</span><span style="color: #001080">now</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #AF00DB">for</span><span style="color: #000000"> </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #AF00DB">await</span><span style="color: #000000"> byte </span><span style="color: #AF00DB">in</span><span style="color: #000000"> bytes {</span></span>
<span class="line"><span style="color: #000000">                    … </span><span style="color: #008000">// Do something with the byte.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">                    unpostedByteCount += </span><span style="color: #098658">1</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #0000FF">let</span><span style="color: #000000"> now = clock.</span><span style="color: #001080">now</span></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #0000FF">let</span><span style="color: #000000"> delta = now - lastUpdate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> (    delta &gt; .</span><span style="color: #795E26">seconds</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">                         || (    (delta &gt; .</span><span style="color: #795E26">milliseconds</span><span style="color: #000000">(</span><span style="color: #098658">100</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">                              &amp;&amp; </span><span style="color: #098658">1_000_000</span><span style="color: #000000"> &lt;= unpostedByteCount))) {</span></span>
<span class="line"><span style="color: #000000">                        byteCount += unpostedByteCount</span></span>
<span class="line"><span style="color: #000000">                        unpostedByteCount = </span><span style="color: #098658">0</span></span>
<span class="line"><span style="color: #000000">                        lastUpdate = now</span></span>
<span class="line"><span style="color: #000000">                    }</span></span>
<span class="line"><span style="color: #000000">                }</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ This isn&#8217;t a complete implementation, as it won&#8217;t update the byte count if the download stalls (since the lack of incoming bytes will mean no iteration on the loop, and therefore no updates even if a full second passes). But it&#8217;s sufficient for demonstration purposes here.</p>



<p>🖐️ Why didn&#8217;t I just use <code><a href="https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/Throttle.md" data-wpel-link="external" target="_blank" rel="external noopener">throttle</a></code> from <a href="https://github.com/apple/swift-async-algorithms" data-wpel-link="external" target="_blank" rel="external noopener">swift-async-algorithms</a>? I did, at first, and quickly discovered that its performance is <em>horrible</em>. While I do suspect I can &#8216;optimise&#8217; it to not be atrocious, I haven&#8217;t pursued that as it was easier to just write my own throttling system.</p>
</div></div>



<p>The above seems fairly straightforward, but if you run it and have any non-trivial I/O rate &#8211; even just a few hundred kilobytes per second &#8211; you&#8217;ll find that it saturates an entire CPU core, not just wasting CPU time but limiting the I/O rate severely.</p>



<p>Using a <code>SuspendingClock</code> makes no difference.</p>



<p>In a nutshell, the problem is that Swift&#8217;s <code><a href="https://developer.apple.com/documentation/swift/clock" data-wpel-link="external" target="_blank" rel="external noopener">Clock</a></code> protocol has significant overheads by design<sup data-fn="2f4a7c64-e213-44df-a3da-0e5020545aad" class="fn"><a href="#2f4a7c64-e213-44df-a3da-0e5020545aad" id="2f4a7c64-e213-44df-a3da-0e5020545aad-link">1</a></sup>. If you look at a time profile of code like this, you&#8217;ll see things like:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="900" height="716" src="https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead.webp" alt="Screenshot of Instruments showing the outline view for a Time Profile, expanded to show dozens of spurious, overhead functions taking up the vast majority of the runtime." class="wp-image-7991" srcset="https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead.webp 900w, https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead-256x204.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead-768x611.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead@2x.webp 1800w, https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead-256x204@2x.webp 512w" sizes="auto, (max-width: 900px) 100vw, 900px" /></figure>
</div>


<p>That&#8217;s a lot of time wasted in function calls and struct initialisation and type conversion and protocol witnesses and all that guff. The only part that&#8217;s <em>actually</em> retrieving the time is the <code><a href="https://github.com/apple/swift/blob/625436af05b1cf8f1904096530235489daec9dac/stdlib/public/Concurrency/Clock.cpp#L30" data-wpel-link="external" target="_blank" rel="external noopener">swift_get_time</a></code> call (which is just a wrapper over <code><a href="https://www.manpagez.com/man/3/clock_gettime/" data-wpel-link="external" target="_blank" rel="external noopener">clock_gettime</a></code>, which is just a wrapper over <code><a href="https://www.manpagez.com/man/3/clock_gettime_nsec_np/" data-wpel-link="external" target="_blank" rel="external noopener">clock_gettime_nsec_np</a>(CLOCK_UPTIME_RAW)</code>, which is just a wrapper over <code><a href="https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time" data-wpel-link="external" target="_blank" rel="external noopener">mach_absolute_time</a></code>).</p>



<p>I wrote <a href="https://github.com/wadetregaskis/Swift-Benchmarks/blob/main/Benchmarks/Clocks/Clocks.swift" data-wpel-link="external" target="_blank" rel="external noopener">some simple benchmarks of various alternative time-tracking methods</a>, with these results with Swift 5.10 (showing the median runtime of the benchmark, which is a million iterations of checking the time):</p>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Method</th><th class="has-text-align-center" data-align="center">10-core iMac Pro</th><th class="has-text-align-center" data-align="center">M2 MacBook Air</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right"><code><a href="https://developer.apple.com/documentation/swift/continuousclock" data-wpel-link="external" target="_blank" rel="external noopener">ContinuousClock</a></code></td><td class="has-text-align-center" data-align="center">429 ms</td><td class="has-text-align-center" data-align="center">258 ms</td></tr><tr><td class="has-text-align-right" data-align="right"><code><a href="https://developer.apple.com/documentation/swift/suspendingclock" data-wpel-link="external" target="_blank" rel="external noopener">SuspendingClock</a></code></td><td class="has-text-align-center" data-align="center">430 ms</td><td class="has-text-align-center" data-align="center">247 ms</td></tr><tr><td class="has-text-align-right" data-align="right"><code><a href="https://developer.apple.com/documentation/foundation/date" data-wpel-link="external" target="_blank" rel="external noopener">Date</a></code></td><td class="has-text-align-center" data-align="center">30 ms</td><td class="has-text-align-center" data-align="center">19 ms</td></tr><tr><td class="has-text-align-right" data-align="right"><code><a href="https://www.manpagez.com/man/3/clock_gettime_nsec_np/" data-wpel-link="external" target="_blank" rel="external noopener">clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)</a></code></td><td class="has-text-align-center" data-align="center">32 ms</td><td class="has-text-align-center" data-align="center">10 ms</td></tr><tr><td class="has-text-align-right" data-align="right"><code><a href="https://www.manpagez.com/man/3/clock_gettime_nsec_np/" data-wpel-link="external" target="_blank" rel="external noopener">clock_gettime_nsec_np(CLOCK_UPTIME_RAW)</a></code></td><td class="has-text-align-center" data-align="center">27 ms</td><td class="has-text-align-center" data-align="center">10 ms</td></tr><tr><td class="has-text-align-right" data-align="right"><code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/gettimeofday.2.html" data-wpel-link="external" target="_blank" rel="external noopener">gettimeofday</a></code></td><td class="has-text-align-center" data-align="center">24 ms</td><td class="has-text-align-center" data-align="center">12 ms</td></tr><tr><td class="has-text-align-right" data-align="right"><code><a href="https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time" data-wpel-link="external" target="_blank" rel="external noopener">mach_absolute_time</a></code></td><td class="has-text-align-center" data-align="center">15 ms</td><td class="has-text-align-center" data-align="center">6 ms</td></tr></tbody></table></figure>



<p>All these alternative methods are <em>well</em> over an order of magnitude faster than Swift&#8217;s native clock APIs, showing just how dreadfully inefficient the Swift <code>Clock</code> API is.</p>



<h3 class="wp-block-heading">mach_absolute_time for the win</h3>



<p>Unsurprisingly, <code>mach_absolute_time</code> is the fastest. It is what all these other APIs are actually based on; it is the lowest level of the time stack.</p>



<p>The downside to calling <code>mach_absolute_time</code> <em>directly</em>, though, is that <a href="https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time#discussion" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s on Apple&#8217;s &#8220;naughty&#8221; list</a> &#8211; apparently it&#8217;s been abused for device fingerprinting, so Apple require you to beg for special permission if you want to use it (even though it&#8217;s used by all these other APIs anyway, as the basis for their implementations, and there&#8217;s nothing you can get from <code>mach_absolute_time</code> that you can&#8217;t get from them too 🤨).</p>



<h3 class="wp-block-heading"><code>Date</code> surprisingly not bad</h3>



<p>I was quite surprised to see good ol&#8217; <code><a href="https://developer.apple.com/documentation/foundation/date" data-wpel-link="external" target="_blank" rel="external noopener">Date</a></code> performing competitively with the traditional C-level APIs, at least on x86-64. Even on arm64 it&#8217;s not bad, at still a third to half the speed of the C APIs. This surprised me because <s>it has the overhead of at least one Objective-C message send (for <code><a href="https://developer.apple.com/documentation/foundation/date/1780473-timeintervalsincenow" data-wpel-link="external" target="_blank" rel="external noopener">timeIntervalSinceNow</a></code>), unless somehow the Swift compiler is optimising that into a static function call, or inlining it entirely…?</s></p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p><strong>Update</strong>: I later looked at the disassembly, and found no message sends, only a plain function call to <code>Foundation.Date.timeIntervalSinceNow.getter</code> (which is only 40 instructions, on arm64, over <code>clock_gettime</code> and <code>__stack_chk_fail</code> &#8211; and the former is hundreds of instructions, so it&#8217;s adding relatively little overhead to the C API).</p>



<p>This isn&#8217;t being done by the compiler, it&#8217;s because <a href="https://github.com/apple/swift-foundation/blob/main/Sources/FoundationEssentials/Date.swift" data-wpel-link="external" target="_blank" rel="external noopener">that&#8217;s <em>actually</em> how it&#8217;s implemented in Foundation</a>. I keep forgetting that Foundation from Swift is no longer just the old Objective-C Foundation, but rather mostly the <em>new</em> Foundation that&#8217;s written in native Swift. So these performance results likely don&#8217;t apply once you go back far enough in Apple OS releases (to when Swift really was calling into the Objective-C code for <code>NSDate</code>) &#8211; but it&#8217;s safe to rely on good <code>Date</code> performance now and in future.</p>
</div></div>



<p>I certainly wouldn&#8217;t be afraid to use <code>Date</code> broadly, going down to lower APIs only when truly necessary &#8211; which is pretty rarely, I&#8217;d wager; we&#8217;re talking a mere 19 to 30 <em>nanoseconds</em> to get the time elapsed since a reference date <em>and</em> compare it to a threshold. If that&#8217;s too slow, it might be an indication that there&#8217;s a bigger problem (like transferring data a single byte at a time, as in the example that started this post &#8211; but more on that in <a href="https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/" data-wpel-link="internal">the next post</a>).</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h3 class="wp-block-heading">Follow-up</h3>



<p>This post <a href="https://news.ycombinator.com/item?id=40262897" data-wpel-link="external" target="_blank" rel="external noopener">got some attention on HackerNews</a>. Pleasingly, the comments there were almost all well-intentioned and interesting. It&#8217;s a bit beyond me to try to address all of them, but a few in particular raised good points that I would like to answer / clarify:</p>



<ul class="wp-block-list">
<li>A lot of folks were curious about <code>mach_absolute_time</code> being on Apple&#8217;s naughty list. I don&#8217;t know for sure why it is either, but I think it&#8217;s very likely that it&#8217;s <em>primarily</em> because it essentially provides a reference time point, that&#8217;s very precise and pretty unique between computers. It&#8217;s not the boot time necessarily &#8211; because the timer pauses whenever the system is put to sleep &#8211; but even so it provides a simple way to nearly if not exactly identify an individual machine session (between boots &amp; sleeps). It probably wouldn&#8217;t take many other fingerprinting data points to reliably pin-point a specific machine.<br><br>Secondarily, because it provides very precise timing capabilities (e.g. nanosecond-resolution on x86), it could possibly be a key component of <a href="https://en.wikipedia.org/wiki/Timing_attack" data-wpel-link="external" target="_blank" rel="external noopener">timing attacks</a> and broader device fingerprinting based on timing information (e.g. measuring how long it takes to perform an otherwise innocuous operation).<br><br>That all said, the only difference between it and some of the higher-level APIs wrapping it is their overhead. And it&#8217;s not apparent to me that merely making the &#8220;get-time&#8221; functionality 2x slower is going to magically mitigate all the above concerns, especially when we&#8217;re still talking just a few nanoseconds.</li>



<li>Admittedly my phrasing regarding Apple&#8217;s policies on <code>mach_absolute_time</code> &#8211; &#8220;beg for permission to use it&#8221; &#8211; is a little melodramatic. It&#8217;s revealing something of my personal opinions on certain Apple &#8220;security&#8221; practices. I love that Apple genuinely care about protecting everyone&#8217;s privacy, but sometimes I chaff at what feels like capricious or impractical specific policies.<br><br>In this particular case, it&#8217;s not apparent to me why this sort of protection is needed for <em>native</em> apps. In a web browser, sure, you&#8217;re running untrustworthy, essentially arbitrary code from all over the place, a <em>lot</em> of which is openly malicious (thanks, Google &amp; Facebook, for your pervasive trackers &#8211; fuck you too). But a native app &#8211; or heck, even a dodgy non-native one like an Electron app &#8211; must be explicitly installed by the end user, among other barriers like code signing.</li>



<li>A few folks looked at the example case, of iterating a single byte at a time, and were suspicious of how performant that could possibly be anyway. This is a very fair reaction &#8211; it&#8217;s my ingrained instinct as well, from years of C/C++/Objective-C &#8211; <em>but</em> it&#8217;s relying on a few outdated assumptions. <a href="https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/" data-wpel-link="internal">My next post</a> already covered this for the most part, but in short here:<br><br>Through inlining, that code basically optimises down to an outer loop that fetches a new <em>chunk</em> of data (a pointer &amp; length) plus an inner loop to iterate over that as direct memory access. The chunks are typically tens of kilobytes to megabytes, in my experience (depending on the source, e.g. network vs local storage, and the buffer sizes chosen by Apple&#8217;s framework code). So it actually is quite performant and essentially what you&#8217;d conventionally write in a file descriptor read loop. <em>If and when</em> it happens to optimise correctly. That&#8217;s the major caveat &#8211; sometimes the Swift compiler fails to properly optimise code like this, and then indeed the performance can really suck. But for simple cases like in this post&#8217;s example code, the optimiser has no trouble with it.</li>



<li>Similarly, a few folks questioned the need to check the clock on <em>every</em> byte, as in the example. That&#8217;s a valid critique of this sort of code in many contexts, and I concur that where possible one <em>should</em> try to be smarter about such things &#8211; i.e. use sequences of bunches of bytes, not sequences of individual bytes.  <a href="https://wadetregaskis.com/urlsession-performance-for-reading-a-byte-stream/" data-wpel-link="internal">e.g. with <code>URLSession</code> you can</a>, and indeed it is faster to do it smarter like that.  But, you <em>can</em> get acceptable real-world performance with this code, even in high-throughput cases, and it&#8217;s relatively simple and intuitive to write, so it&#8217;s not uncommon or necessarily unreasonable.<br><br>In addition, sometimes you&#8217;re at the mercy of the APIs available &#8211; e.g. sometimes you can <em>only</em> get an <code>AsyncSequence&lt;UInt8&gt;</code>. If you don&#8217;t care about complete accuracy, you can do things like only considering UI updates every N bytes. You&#8217;ll save CPU time and nobody will notice the difference for small enough N on a fast enough iteration, but if those prerequisites aren&#8217;t met you might read e.g. N-1 bytes and then hit a long pause, during which time you <em>have</em> the extra N-1 bytes in hand but you&#8217;re not showing as such in your UI.</li>



<li>Some folks noted that are a <em>lot</em> of other clock APIs from Apple&#8217;s frameworks, like <code><a href="https://developer.apple.com/documentation/dispatch/dispatchtime" data-wpel-link="external" target="_blank" rel="external noopener">DispatchTime</a></code> and <code><a href="https://developer.apple.com/documentation/quartzcore/1395996-cacurrentmediatime" data-wpel-link="external" target="_blank" rel="external noopener">CACurrentMediaTime</a></code>. I didn&#8217;t include those in the benchmark because I just didn&#8217;t think of them at the time. If anyone wants to send me a pull request adding them to <a href="https://github.com/wadetregaskis/Swift-Benchmarks/blob/main/Benchmarks/Clocks/Clocks.swift" data-wpel-link="external" target="_blank" rel="external noopener">the code</a>, I&#8217;d be very happy to accept it.<br><br>I haven&#8217;t checked all those other APIs specifically, but I can pretty much guarantee they&#8217;re all built on <code>mach_absolute_time</code> too (possibly via one or more of the other C APIs already covered in this post). In fact those two examples just mentioned are explicitly documented as using <code>mach_absolute_time</code>.</li>



<li><a href="https://news.ycombinator.com/user?id=Kallikrates" data-wpel-link="external" target="_blank" rel="external noopener">Kallikrates</a> quietly pointed to a very interesting recent change in Apple&#8217;s Swift standard library code, <a href="https://github.com/apple/swift/pull/73429" data-wpel-link="external" target="_blank" rel="external noopener">Make static [milli/micro/nano]seconds members on Duration inlinable</a>. It&#8217;s paired with <a href="https://github.com/apple/swift/pull/73419" data-wpel-link="external" target="_blank" rel="external noopener">another patch</a> that together seem very specifically aimed at eliminating some of the absurd overhead in Swift&#8217;s <code>ContinuousClock</code> &amp; <code>SuspendingClock</code> implementations. The timing is a bit interesting &#8211; I don&#8217;t know if they were prompted by this post, but it&#8217;d be an unlikely coincidence otherwise.<br><br>In any case, I suspect it is possible to eliminate the overheads &#8211; there&#8217;s no apparent reason why they can&#8217;t be at least as efficient as <code>Date</code> already is &#8211; and so I hope that is what&#8217;s happening. Hopefully I&#8217;ll be able to re-run these benchmarks in a few months, with Swift 6, and see the performance gap eliminated. 🤞</li>
</ul>


<ol class="wp-block-footnotes"><li id="2f4a7c64-e213-44df-a3da-0e5020545aad">One might quibble with the &#8220;by design&#8221; assertion.  What I mean is that because it uses a protocol it&#8217;s susceptible to significant overheads &#8211; as is seen in these benchmarks &#8211; and because its internal implementation (a private <code>_Int128</code> type, inside the standard library) is kept hidden, it limits the compiler&#8217;s ability to inline, which is in turn critical to eliminating what&#8217;s technically a lot of boilerplate.  In contrast, if it were simply a struct using only public types internally, it would have avoided most of these overheads and been more amenable to inlining.<br><br>It&#8217;s not an irredeemable design (I think) &#8211; and that&#8217;s what the <a href="https://github.com/apple/swift/pull/73429" data-wpel-link="external" target="_blank" rel="external noopener">recent</a> <a href="https://github.com/apple/swift/pull/73419" data-wpel-link="external" target="_blank" rel="external noopener">patches</a> seem to be banking on, by tweaking the design in order to allow inlining and thus hopefully eliminate almost all the overhead. <a href="#2f4a7c64-e213-44df-a3da-0e5020545aad-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swifts-native-clocks-are-very-inefficient/feed/</wfw:commentRss>
			<slash:comments>13</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/05/ContinuousClock-overhead.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7990</post-id>	</item>
		<item>
		<title>Matching prefixes in Swift strings</title>
		<link>https://wadetregaskis.com/matching-prefixes-in-swift-strings/</link>
					<comments>https://wadetregaskis.com/matching-prefixes-in-swift-strings/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 02 May 2024 01:46:02 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[NSString]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[Unicode]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7953</guid>

					<description><![CDATA[How do you determine if one string starts with another, in Swift? Surely that&#8217;s exactly what the hasPrefix(_:) method is for: No can haz etiquette? Wot? The problem is that hasPrefix is not meant for general use with human text; it&#8217;s barely better than a byte-wise comparison. It only guarantees that it won&#8217;t be fooled&#8230; <a class="read-more-link" href="https://wadetregaskis.com/matching-prefixes-in-swift-strings/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>How do you determine if one string starts with another, in Swift?</p>



<p>Surely that&#8217;s exactly what the <code><a href="https://developer.apple.com/documentation/swift/substring/hasprefix(_:)" data-wpel-link="external" target="_blank" rel="external noopener">hasPrefix(_:)</a></code> method is for:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> greeting = </span><span style="color: #A31515">&quot;Hello&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> sentence = </span><span style="color: #A31515">&quot;hello, this is dog.&quot;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> sentence.</span><span style="color: #795E26">hasPrefix</span><span style="color: #000000">(greeting) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Hi!  Nice to meet you!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;No can haz etiquette?&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>No can haz etiquette?</code></p></blockquote></figure>



<p>Wot?</p>



<p>The problem is that <code>hasPrefix</code> is not meant for general use with <em>human</em> text; it&#8217;s barely better than a byte-wise comparison. It <em>only</em> guarantees that it won&#8217;t be fooled by mere differences in Unicode encoding, which is a good start, but not remotely sufficient for general use.</p>



<p>Let&#8217;s step back a bit, and first consider the slightly simpler case of just comparing two whole strings. We can worry about the prefix-matching aspect later.</p>



<p><code><a href="https://developer.apple.com/documentation/foundation/nsstring" data-wpel-link="external" target="_blank" rel="external noopener">NSString</a></code> (from <a href="https://developer.apple.com/documentation/foundation" data-wpel-link="external" target="_blank" rel="external noopener">Foundation</a>) provides Swift <code><a href="https://developer.apple.com/documentation/swift/string" data-wpel-link="external" target="_blank" rel="external noopener">String</a></code>s with a variety of more powerful comparison methods, such as <code><a href="https://developer.apple.com/documentation/foundation/nsstring/1414769-caseinsensitivecompare" data-wpel-link="external" target="_blank" rel="external noopener">caseInsensitiveCompare(_:)</a></code> (which is really just an alias for <code><a href="https://developer.apple.com/documentation/foundation/nsstring/1414561-compare" data-wpel-link="external" target="_blank" rel="external noopener">compare(_:options:range:locale:)</a></code> with the <code><a href="https://developer.apple.com/documentation/foundation/nsstring/compareoptions/1412382-caseinsensitive" data-wpel-link="external" target="_blank" rel="external noopener">caseInsensitive</a></code> option).</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> a = </span><span style="color: #A31515">&quot;hello&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> b = </span><span style="color: #A31515">&quot;Hello&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> a.</span><span style="color: #795E26">caseInsensitiveCompare</span><span style="color: #000000">(b) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Hello indeed.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Hmph… rude.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Hello indeed.</code></p></blockquote></figure>



<p>So that works. For <em>case sensitivity</em>. But what about other situations?</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> plain = </span><span style="color: #A31515">&quot;cafe&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> fancy = </span><span style="color: #A31515">&quot;café&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> plain.</span><span style="color: #795E26">caseInsensitiveCompare</span><span style="color: #000000">(fancy) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Right, either way it&#39;s a shop that sells coffee.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;So… no coffee, then?&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>So… no coffee, then?</code></p></blockquote></figure>



<p>Well, shit.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>It may vary in other languages, but in English &#8220;café&#8221; is just an alternative spelling of &#8220;cafe&#8221;, and you almost always want to consider them equal. In fact, in English it&#8217;s basically never really required that you observe accents on letters &#8211; some are <em>technically</em> required, such as blasé, but English speakers are very blase about such things. Unlike e.g. Spanish, with n vs ñ, accented letters are not considered <em>distinct</em> letters in English.</p>



<p>But, letter accents may creep into English text anyway (just like spoken accents). Some people prefer them, for any of numerous reasons, like:</p>



<ul class="wp-block-list">
<li>In proper nouns out of respect for the so-named.</li>



<li>To honour words&#8217; roots in other languages.</li>



<li>For technical correctness of pronunciation.</li>



<li>Just aesthetically.</li>
</ul>



<p>So you do need to <em>support</em> them, which means accepting and preserving them, but (usually) otherwise ignoring them.</p>
</div></div>



<p>Ah, but wait, <a href="https://developer.apple.com/documentation/foundation/nsstring/1414769-caseinsensitivecompare" data-wpel-link="external" target="_blank" rel="external noopener">the documentation for <code>caseInsensitiveCompare(_:)</code></a> has a footnote which surely addresses exactly this problem, albeit obliquely:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Important</strong></p>



<p>When working with text that’s presented to the user, use the&nbsp;<a href="https://developer.apple.com/documentation/foundation/nsstring/1417333-localizedcaseinsensitivecompare" data-wpel-link="external" target="_blank" rel="external noopener"><code>localizedCaseInsensitiveCompare(_:)</code></a>&nbsp;method instead.</p>
</blockquote>



<p>No worries &#8211; we&#8217;ll just use that instead:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> plain = </span><span style="color: #A31515">&quot;cafe&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> fancy = </span><span style="color: #A31515">&quot;café&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> plain.</span><span style="color: #795E26">localizedCaseInsensitiveCompare</span><span style="color: #000000">(fancy) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Finally!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Oh come on!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Oh come on!</code></p></blockquote></figure>



<p>It turns out this mistake is made by <em>most</em> of the <code>String</code> / <code>NSString</code> methods of similar ilk. And the discrepancies are inscrutable &#8211; e.g. <code><a href="https://developer.apple.com/documentation/swift/stringprotocol/localizedstandardcompare(_:)" data-wpel-link="external" target="_blank" rel="external noopener">localizedStandardCompare(_:)</a></code> doesn&#8217;t handle accents correctly but <code><a href="https://developer.apple.com/documentation/foundation/nsstring/1413574-localizedstandardrange" data-wpel-link="external" target="_blank" rel="external noopener">localizedStandardRange(of:)</a></code> does.</p>



<p>Long story short, you need to base most (if not all) your string comparison on <a href="https://developer.apple.com/documentation/foundation/nsstring/1414561-compare" data-wpel-link="external" target="_blank" rel="external noopener"><code>compare(_:options:range:locale:)</code> </a>or its sibling <code><a href="https://developer.apple.com/documentation/foundation/nsstring/1417348-range" data-wpel-link="external" target="_blank" rel="external noopener">range(of:options:range:locale:)</a></code>, because the other string methods don&#8217;t work properly.</p>



<p>So, with <code>compare(…)</code> you can do e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> plain = </span><span style="color: #A31515">&quot;cafe&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> fancy = </span><span style="color: #A31515">&quot;Café&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> .</span><span style="color: #001080">orderedSame</span><span style="color: #000000"> == plain.</span><span style="color: #795E26">compare</span><span style="color: #000000">(fancy,</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Finally!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;😤&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Finally</code>.</p></blockquote></figure>



<p>But there&#8217;s two other options you should almost always use, which are easy to overlook:</p>



<ul class="wp-block-list">
<li><code><a href="https://developer.apple.com/documentation/foundation/nsstring/compareoptions/1409350-widthinsensitive" data-wpel-link="external" target="_blank" rel="external noopener">widthInsensitive</a></code>.<br><br>In all the Latin-alphabet languages of which I&#8217;m aware, there is no notion of &#8220;width&#8221; and therefore no issues with width [in]sensitivity. It seems it most-often comes up in Japanese, where for historical reasons there were multiple versions <em>of the same character</em> that merely different in their visual dimensions. e.g. &#8220;カ&#8221; and &#8220;ｶ&#8221;. They are semantically the <em>exact</em> same character, even moreso than &#8220;a&#8221; is to &#8220;A&#8221;.<br><br>Even if the locale uses a Latin alphabet, there may still be mixed character sets and languages in the text your app processes &#8211; e.g. someone writing mostly in English but including Japanese names.</li>



<li><code><a href="https://developer.apple.com/documentation/foundation/nsstring/compareoptions/1415530-numeric" data-wpel-link="external" target="_blank" rel="external noopener">numeric</a></code>.<br><br>There are <a href="https://en.wikipedia.org/wiki/Arabic_numerals#Comparison_with_other_digits" data-wpel-link="external" target="_blank" rel="external noopener">more numeric systems than just the modern Arabic numerals</a> as used in English. e.g. &#8220;٤٢&#8221; is 42 in Eastern Arabic. What matters is usually their <em>meaning</em> (i.e. numeric value), not their representation, just like the other factors we&#8217;ve already covered.</li>
</ul>



<p>So, incorporating all that, the magic incantation required to <em>correctly</em> compare two human pieces of English text, in Swift, is:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> plain = </span><span style="color: #A31515">&quot;cafe カ 42&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> fancy = </span><span style="color: #A31515">&quot;Café ｶ ٤٢&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> .</span><span style="color: #001080">orderedSame</span><span style="color: #000000"> == plain.</span><span style="color: #795E26">compare</span><span style="color: #000000">(fancy,</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Actually equivalent.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Not equivalent&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Actually equivalent.</code></p></blockquote></figure>



<p>Note that the explicit <code>locale</code> argument may be important for some use-cases, for two reasons:</p>



<ul class="wp-block-list">
<li><em>Generally</em> it seems to turn on <em>some</em> &#8211; but not all &#8211; locale-appropriate options, in addition to any you specify explicitly.<br><br>While that may be redundant when you&#8217;re explicitly turning them on anyway, it&#8217;s possible it will have <em>additional</em> effects that aren&#8217;t expressible with the <code>options</code> parameter. You&#8217;ll probably want those too, as if they exist they&#8217;ll be things like special handling of unusual cases and exceptions to the rules.</li>



<li><em>Sometimes</em> it turns hidden options <em>off</em>, such as whether to consider superscripts &amp; subscripts equivalent. This might be a reason to <em>not</em> use it sometimes, if you don&#8217;t like the end result.</li>
</ul>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>It&#8217;s less clear, even just considering English, what the correct default behaviour is regarding superscripts and subscripts, or &#8220;baseline sensitivity&#8221;. It&#8217;s quite conceivable that a user might intend to match a superscript or subscript even though they entered a plain digit, because most people don&#8217;t know how to actually type superscripts and subscripts (it&#8217;s not easy on most computers, at least not without 3rd party utilities like <a href="https://matthewpalmer.net/rocket/" data-wpel-link="external" target="_blank" rel="external noopener">Rocket</a>, and practically impossible on mobile devices).<br><br>And plenty of programs &#8211; particularly those not written in Swift, that might not handle Unicode correctly even at the most basic levels &#8211; erroneously devolve superscripts and subscripts to plain digits, which ideally wouldn&#8217;t prevent subsequent tools from still working with them (e.g. still finding &#8220;but1&#8221; when looking for &#8220;but¹&#8221;).<br><br>Yet in some contexts the differences very much do matter &#8211; e.g. in mathematical notation, x² is <em>very</em> different to x₂.</p>
</div></div>



<p>For reference, here&#8217;s a breakdown of the behaviour of some key <code>String</code> / <code>NSString</code> methods (as tested in the en_AU locale), to help you decide what specific incantation you need in a given situation:</p>



<figure class="wp-block-table"><table><thead><tr><th class="has-text-align-left" data-align="left">Method</th><th class="has-text-align-center" data-align="center">Case insensitive<br>(&#8220;Hello&#8221; vs &#8220;hello&#8221;)</th><th class="has-text-align-center" data-align="center">Diacritic insensitive<br>(&#8220;cafe&#8221; vs &#8220;café&#8221;)</th><th class="has-text-align-center" data-align="center">Width insensitive<br>(&#8220;カ&#8221; vs &#8220;ｶ&#8221;)</th><th class="has-text-align-center" data-align="center">Numerals insensitive<br>(&#8220;42&#8221; vs &#8220;٤٢&#8221;)</th><th class="has-text-align-center" data-align="center">Baseline insensitive<br>(&#8220;but1&#8221; vs &#8220;but¹&#8221;)</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><code>==</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>hasPrefix</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>commonPrefix(…, options: .caseInsensitive)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>commonPrefix(…, options: .diacriticInsensitive)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>commonPrefix(…, options: .widthInsensitive)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>commonPrefix(…, options: .numeric)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌<sup data-fn="0a002391-051b-43f5-90c2-52da80012369" class="fn"><a href="#0a002391-051b-43f5-90c2-52da80012369" id="0a002391-051b-43f5-90c2-52da80012369-link">1</a></sup></td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td class="has-text-align-left" data-align="left"><code>localizedCompare</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>localizedCaseInsensitiveCompare</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>localizedStandardCompare</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>localizedStandardRange(of:)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .caseInsensitive)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .caseInsensitive, locale: .current)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .diacriticInsensitive)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .diacriticInsensitive, locale: .current)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .widthInsensitive)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .widthInsensitive, locale: .current)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .numeric)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: .numeric, locale: .current)</code></td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">❌</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌<sup data-fn="6ac6f3c3-cfbc-4d7e-a4ec-b02e3304d489" class="fn"><a href="#6ac6f3c3-cfbc-4d7e-a4ec-b02e3304d489" id="6ac6f3c3-cfbc-4d7e-a4ec-b02e3304d489-link">2</a></sup></td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: [.caseInsensitive, .diacriticInsensitive, .numeric, .widthInsensitive])</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td></tr><tr><td class="has-text-align-left" data-align="left"><code>compare(…, options: [.caseInsensitive, .diacriticInsensitive, .numeric, .widthInsensitive], locale: .current)</code></td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">✅</td><td class="has-text-align-center" data-align="center">❌</td></tr></tbody></table></figure>



<p>Now, the real challenge is making code that works across <em>all</em> locales. In a nutshell, that&#8217;s practically impossible with Swift&#8217;s standard libraries today &#8211; they just don&#8217;t support it. To do it right, you&#8217;d have to determine what the appropriate comparison options are for every possible locale, manually, and bundle that database with your app.</p>



<p>But, given it&#8217;s usually better anyway to err on the side of matching rather than not matching, you can get pretty far by just assuming insensitivity to the above five factors.</p>



<p>Even in cases where this does cause mistakes &#8211; e.g. conflating &#8220;Maßen&#8221; (<em>in moderation</em>) with &#8220;Massen&#8221; (<em>en masse</em>) in German &#8211; it&#8217;s potentially unavoidable without deeper, context-specific knowledge anyway (since &#8220;ß&#8221; <em>is</em> normally equivalent to &#8220;ss&#8221; in German, just not regarding those specific two words &#8211; you can read more about this unpleasant situation on e.g. <a href="https://en.wikipedia.org/wiki/German_alphabet#Sharp_s" data-wpel-link="external" target="_blank" rel="external noopener">Wikipedia</a>).</p>



<h2 class="wp-block-heading">So, back to prefixes…</h2>



<p>Fortunately, <code>compare(_:options:range:locale:)</code> and <code>range(of:options:range:locale:)</code> have a couple of additional options which make them easier to apply to situations other than just comparing whole strings.</p>



<h3 class="wp-block-heading">Checking for a specific prefix</h3>



<p>There is an <code><a href="https://developer.apple.com/documentation/foundation/nsstring/compareoptions/1410321-anchored" data-wpel-link="external" target="_blank" rel="external noopener">anchored</a></code> option which is perfect for this &#8211; it restricts the match to the <em>start</em> of the receiving string. e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> reaction = </span><span style="color: #A31515">&quot;😁👍&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">nil</span><span style="color: #000000"> != reaction.</span><span style="color: #001080">range</span><span style="color: #000000">(</span><span style="color: #795E26">of</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;😁&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                         </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">anchored</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                   .</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                   .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                   .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                   .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                         </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">)) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Happy!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Sad¡&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Happy!</code></p></blockquote></figure>



<p>Note that you <em>must</em> use the <code>range(of:…)</code> variant, not <code>compare(…)</code>, because the latter essentially requires that the two strings <em>fully</em> match, not merely that one is a prefix of the other (more on that <a href="#beware-the-range-parameter">later</a>, in case you&#8217;re not convinced).</p>



<h3 class="wp-block-heading">Finding common prefixes</h3>



<p>Fortunately, there&#8217;s a convenience method for exactly this, <code><a href="https://developer.apple.com/documentation/foundation/nsstring/1408169-commonprefix" data-wpel-link="external" target="_blank" rel="external noopener">commonPrefix(with:options:)</a></code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> happy = </span><span style="color: #A31515">&quot;😁👍&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> party = </span><span style="color: #A31515">&quot;😁🎉&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Similarities:&quot;</span><span style="color: #000000">, happy.</span><span style="color: #795E26">commonPrefix</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000">: party,</span></span>
<span class="line"><span style="color: #000000">                                          </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                    .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                    .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                    .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">]))</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Similarities: 😁</code></p></blockquote></figure>



<p>Do <em>not</em> use this if you merely want to see if they share a <em>specific</em> prefix, because:</p>



<ul class="wp-block-list">
<li>It&#8217;s more efficient to just check that directly on each string separately (rather than allocating and returning an intermediary string).</li>



<li>You still have to use <code>compare(…)</code> with the full set of options to check the result.</li>
</ul>



<p>Note also that it does not have a locale parameter, so you cannot opt in to any system-default options defined for the current locale; you must explicitly specify <em>every</em> option you need.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ Beware: it doesn&#8217;t honour the <code>numeric</code> option.</p>
</div></div>



<h2 class="wp-block-heading">Working with suffixes</h2>



<p>You can of course reverse both strings and then compare what are now their prefixes, but this is expensive and awkward, since the result of <code>String</code>&#8216;s <code><a href="https://developer.apple.com/documentation/swift/emptycollection/reversed()" data-wpel-link="external" target="_blank" rel="external noopener">reversed()</a></code> method is a <code>ReversedCollection&lt;String&gt;</code>, not a <code>String</code> or even a <code>Substring</code>, and it does not have the necessary comparison methods, so you have to convert it to a real <code>String</code> first.</p>



<p>Far easier and more efficient is to make use the <code><a href="https://developer.apple.com/documentation/foundation/nsstring/compareoptions/1415204-backwards" data-wpel-link="external" target="_blank" rel="external noopener">backwards</a></code> option to <code>range(of:…)</code>. e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> word = </span><span style="color: #A31515">&quot;doing&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">nil</span><span style="color: #000000"> != word.</span><span style="color: #001080">range</span><span style="color: #000000">(</span><span style="color: #795E26">of</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;ing&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                     </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">anchored</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                               .</span><span style="color: #001080">backwards</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                               .</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                               .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                               .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                               .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                     </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">)) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;It&#39;s an &#39;ing&#39; word.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Nyet.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>It's an 'ing' word.</code></p></blockquote></figure>



<p>Note how &#8211; conveniently &#8211; it does not require reversal of the argument string (&#8220;ing&#8221; in the above example).</p>



<h2 class="wp-block-heading" id="beware-the-range-parameter">Beware the <code>range</code> parameter</h2>



<p>The <code>compare(…)</code> and <code>range(of:…)</code> methods also have a <code>range</code> parameter. This seems like a great idea &#8211; you can specify which specific subset of a string you care about, without having to actually break it out into a whole new <code>String</code> instance.</p>



<p>However, the <code>range</code> parameter is both a little unintuitive in its behaviour and fundamentally hard to use correctly.</p>



<p>On the first aspect, it&#8217;s critical to realise that it specifies the range within <em>only</em> the receiver (&#8220;happy&#8221; in the example below). It has no effect on the argument string (&#8220;party&#8221; in the example below). So you might innocently write the following:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> happy = </span><span style="color: #A31515">&quot;😁👍&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> party = </span><span style="color: #A31515">&quot;😁🎉&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> .</span><span style="color: #001080">orderedSame</span><span style="color: #000000"> == happy.</span><span style="color: #795E26">compare</span><span style="color: #000000">(party,</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">range</span><span style="color: #000000">: happy.</span><span style="color: #001080">startIndex</span><span style="color: #000000"> ..&lt; happy.</span><span style="color: #795E26">index</span><span style="color: #000000">(</span><span style="color: #795E26">after</span><span style="color: #000000">: happy.</span><span style="color: #001080">startIndex</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">)) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Grins all round.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;…not happy?&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>…not happy?</code></p></blockquote></figure>



<p>If you want to compare subsets of <em>both</em> strings, you need to explicitly slice the second string, e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> happy = </span><span style="color: #A31515">&quot;😁👍&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> party = </span><span style="color: #A31515">&quot;😁🎉&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> .</span><span style="color: #001080">orderedSame</span><span style="color: #000000"> == happy.</span><span style="color: #795E26">compare</span><span style="color: #000000">(party.</span><span style="color: #795E26">prefix</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">range</span><span style="color: #000000">: happy.</span><span style="color: #001080">startIndex</span><span style="color: #000000"> ..&lt; happy.</span><span style="color: #795E26">index</span><span style="color: #000000">(</span><span style="color: #795E26">after</span><span style="color: #000000">: happy.</span><span style="color: #001080">startIndex</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                 </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">)) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Grins all round.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;…not happy?&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Grins all round.</code></p></blockquote></figure>



<p>Or, more simply:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> happy = </span><span style="color: #A31515">&quot;😁👍&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> party = </span><span style="color: #A31515">&quot;😁🎉&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> .</span><span style="color: #001080">orderedSame</span><span style="color: #000000"> == happy.</span><span style="color: #795E26">prefix</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">).</span><span style="color: #795E26">compare</span><span style="color: #000000">(party.</span><span style="color: #795E26">prefix</span><span style="color: #000000">(</span><span style="color: #098658">1</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">)) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Grins all round.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;…not happy?&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Grins all round.</code></p></blockquote></figure>



<p>But, you should rarely if ever actually do the above, because of the second aspect: slicing strings is actually really hard. Not technically, obviously, but if you want to do it <em>correctly</em>. The crux of the challenge is that two strings can have <em>different lengths</em> but still be equivalent (e.g. &#8220;ß&#8221; and &#8220;ss&#8221; in German), so slicing them independently is error-prone, unless you somehow account for the specific differences in their encoding. If you naively assume things like a specific length for a target string (e.g. the single character of &#8220;ß&#8221;) and apply that length to the input string, you might get incorrect results. e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> input = </span><span style="color: #A31515">&quot;Füssen&quot;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> target = </span><span style="color: #A31515">&quot;Füß&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> .</span><span style="color: #001080">orderedSame</span><span style="color: #000000"> == input.</span><span style="color: #795E26">prefix</span><span style="color: #000000">(target.</span><span style="color: #001080">count</span><span style="color: #000000">).</span><span style="color: #795E26">compare</span><span style="color: #000000">(target,</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">options</span><span style="color: #000000">: [.</span><span style="color: #001080">caseInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                                .</span><span style="color: #001080">diacriticInsensitive</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                                .</span><span style="color: #001080">numeric</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                                .</span><span style="color: #001080">widthInsensitive</span><span style="color: #000000">],</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">locale</span><span style="color: #000000">: .</span><span style="color: #001080">current</span><span style="color: #000000">)) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Something about feet.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Nothing afoot.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-pullquote"><blockquote><p><code>Nothing afoot.</code></p></blockquote></figure>



<p>(in case you don&#8217;t speak German, that&#8217;s the <em>wrong</em> result logically &#8211; Füß <em>is</em> a prefix of Füssen)</p>


<ol class="wp-block-footnotes"><li id="0a002391-051b-43f5-90c2-52da80012369">Yes, really.  I have no idea why <code>commonPrefix(with:options:)</code> doesn&#8217;t work correctly with the <code>numeric</code> option, given it presumably uses <code>compare(_:options:range:locale:)</code> or <code>range(of:options:range:locale:)</code> under the hood.  Possibly some bad interaction with locale-specific settings, given it doesn&#8217;t let you specify the locale and it doesn&#8217;t document what it hard-codes it to. <a href="#0a002391-051b-43f5-90c2-52da80012369-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="6ac6f3c3-cfbc-4d7e-a4ec-b02e3304d489">Yes, really.  I don&#8217;t know why using the current locale (en_AU in this case) turns <em>off</em> baseline insensitivity when the <code>numeric</code> option is used, whereas it turns it <em>on</em> otherwise.  Seems like a bug in Apple&#8217;s framework. <a href="#6ac6f3c3-cfbc-4d7e-a4ec-b02e3304d489-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/matching-prefixes-in-swift-strings/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7953</post-id>	</item>
		<item>
		<title>getBitmapDataPlanes can break NSImages &#038; NSBitmapImageReps</title>
		<link>https://wadetregaskis.com/getbitmapdataplanes-can-break-nsimages-nsbitmapimagereps/</link>
					<comments>https://wadetregaskis.com/getbitmapdataplanes-can-break-nsimages-nsbitmapimagereps/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 21 Mar 2024 04:44:26 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[10-bit]]></category>
		<category><![CDATA[bitmapData]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[getBitmapDataPlanes]]></category>
		<category><![CDATA[NSBitmapImageRep]]></category>
		<category><![CDATA[NSImage]]></category>
		<category><![CDATA[Sad]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7889</guid>

					<description><![CDATA[Today was one of those days where you plan to real make a dent in your todo list, and end up spending the entire day debugging why the hell some images are suddenly rendering as completely opaque black. Long story short, on at least some bitmap images, as soon as you call getBitmapDataPlanes it somehow&#8230; <a class="read-more-link" href="https://wadetregaskis.com/getbitmapdataplanes-can-break-nsimages-nsbitmapimagereps/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Today was one of those days where you plan to real make a dent in your todo list, and end up spending the entire day debugging why the hell some images are suddenly rendering as completely opaque black.</p>



<p>Long story short, on at least some bitmap images, as soon as you call <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395490-getbitmapdataplanes" data-wpel-link="external" target="_blank" rel="external noopener">getBitmapDataPlanes</a></code> it somehow permanently breaks that <code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> and <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code>.  The telltale sign of this &#8211; aside from the image rendering incorrectly &#8211; is the stderr message from AppKit:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><code>Failed to extract pixel data from NSBitmapImageRep. Error: -21778</code></p>
</blockquote>



<p>This definitely occurs with 10-bit AVIFs, but it&#8217;s not limited to that bit depth nor that file format, because I found <a href="https://developer.apple.com/forums/thread/700010" data-wpel-link="external" target="_blank" rel="external noopener">the <em>one</em> other mention of this error message online</a>, where the source image was 1-bit.</p>



<p>I&#8217;ve never seen this happen with 8-bit, 12-bit, or 16-bit images, AVIF or otherwise.</p>



<p>This makes me suspect it&#8217;s tied to the internal pixel format &#8211; 10-bit AVIFs always load as 10 / 40 (bits per sample / bits per pixel), irrespective of whether they have an alpha channel.  8-bit AVIFs are always 8 / 32, 12-bit are always 12 / 48.  Maybe <code>NSBitmapImageRep</code> has a bug when the bits per pixel isn&#8217;t a multiple of 16?</p>



<p>If you call <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395421-bitmapdata" data-wpel-link="external" target="_blank" rel="external noopener">bitmapData</a></code> instead you get the same error message but it does <em>not</em> stop the <code>NSImage</code> / <code>NSBitmapImageRep</code> from rendering correctly.  But the pointer returned from <code>bitmapData</code> points to opaque black.  It&#8217;s particularly weird that it returns a pointer to a valid memory allocation, of at least the expected size, yet the contents are nothing but zeroes.  Seems like it pre-allocates some output buffer, as zeroed memory, and then fails to actually write to that buffer.  Yet it returns it anyway, instead of returning nil. 😕</p>



<p>FB13693411.</p>



<h2 class="wp-block-heading">Partial workaround</h2>



<p>If you call <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519890-recache" data-wpel-link="external" target="_blank" rel="external noopener">recache</a></code> on the <code>NSImage</code> afterwards, the image is capable of rendering correctly again.  That doesn&#8217;t help if your objective is to access the bitmap bytes, but at least if you don&#8217;t &#8211; e.g. you&#8217;re encountering this only because some library code is triggering the bug &#8211; you might be able to work around it through <code>recache</code>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/getbitmapdataplanes-can-break-nsimages-nsbitmapimagereps/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/03/Rainbow-Stitch-10-bit-sRGB.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7889</post-id>	</item>
		<item>
		<title>Including Services in contextual menus in SwiftUI</title>
		<link>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/</link>
					<comments>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 19 Mar 2024 01:35:39 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Contextual Menus]]></category>
		<category><![CDATA[Electron]]></category>
		<category><![CDATA[NSHostingView]]></category>
		<category><![CDATA[NSMenu]]></category>
		<category><![CDATA[NSMenuItem]]></category>
		<category><![CDATA[NSServicesMenuRequestor]]></category>
		<category><![CDATA[NSViewRepresentable]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7861</guid>

					<description><![CDATA[SwiftUI provides a way to provide a contextual menu for a view, with contextMenu(menuItems:) and friends, but it requires you to manually specify the entire contents of the contextual menu. That means it does not include the standard Services submenu. A brief history of Contextual Menus Contextual menus were introduced [to the Mac] in 1997&#8230; <a class="read-more-link" href="https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>SwiftUI provides a way to provide a contextual menu for a view, with <code><a href="https://developer.apple.com/documentation/swiftui/view/contextmenu(menuitems:)" data-wpel-link="external" target="_blank" rel="external noopener">contextMenu(menuItems:)</a></code> and friends, but it requires you to manually specify the <em>entire</em> contents of the contextual menu.  That means it does not include the standard Services submenu.</p>



<h2 class="wp-block-heading">A brief history of Contextual Menus</h2>



<p>Contextual menus were introduced [to the Mac] in 1997 with <a href="https://infinitemac.org/1997/Mac%20OS%208.0" data-wpel-link="external" target="_blank" rel="external noopener">Mac OS 8</a>, with the new Contextual Menu Manager system extension and associated <a href="http://preserve.mactech.com/articles/mactech/Vol.14/14.02/ContextualMenuModules/index.html" data-wpel-link="external" target="_blank" rel="external noopener">Contextual Menu Modules</a>.  See also <a href="https://preterhuman.net/macstuff/techpubs/mac/pdf/HIGOS8Guidelines.pdf" data-wpel-link="external" target="_blank" rel="external noopener">the Mac OS 8 edition of the HIG</a> (page 93).</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="354" height="436" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-Finder-file-contextual-menu.webp" alt="" class="wp-image-7862" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-Finder-file-contextual-menu.webp 354w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-Finder-file-contextual-menu-208x256.webp 208w" sizes="auto, (max-width: 354px) 100vw, 354px" /></figure>
</div>


<p>Support varied a lot in the early days, though, and some user-interface conventions didn&#8217;t solidify until a few years later.  e.g. SimpleText didn&#8217;t support contextual menus at all in Mac OS 8.0, and while 3rd party apps like BBEdit eventually did, it took longer still for now-standard items to become commonplace, like Cut / Copy / Paste.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="820" height="404" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu.webp" alt="" class="wp-image-7863" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu.webp 820w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu-256x126.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu-768x378.webp 768w, https://wadetregaskis.com/wp-content/uploads/2024/03/Mac-OS-8-BBEdit-5-1-1-contextual-menu-256x126@2x.webp 512w" sizes="auto, (max-width: 820px) 100vw, 820px" /></figure>
</div>


<p>Broadly-speaking, contextual menus have changed very little over the decades.  At some point the app-specific commands were separated from the system-wide commands, the latter becoming relegated to the current &#8220;Services&#8221; submenu.  And the specific menu items have of course evolved over time, but the basic idea has persisted: to provide app-specific or built-in commands followed by commands proffered by system-wide extensions.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="728" height="656" src="https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu.webp" alt="Screenshot from macOS 14.3.1 Sonoma showing an untitled TextEdit window containing the text &quot;Old Macs never die, they just fade away&quot;. &quot;Fade&quot; is selected, and a contextual menu is popped up from it. The Services menu item is highlighted, with the submenu also open, listing various Apple and 3rd party service extensions." class="wp-image-7864" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu.webp 728w, https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu-256x231.webp 256w, https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu@2x.webp 1456w, https://wadetregaskis.com/wp-content/uploads/2024/03/macOS-14-3-1-Sonoma-TextEdit-contextual-menu-256x231@2x.webp 512w" sizes="auto, (max-width: 728px) 100vw, 728px" /></figure>
</div>


<p>It&#8217;s chuckle-worthy to remember that when Mac OS 8, with Contextual Menus, was introduced, Macs still had one-button mice.  I don&#8217;t recall but assume it was <em>possible</em> to connect a mouse with multiple buttons, to a Mac, but remember that no Macs had USB at this time (that didn&#8217;t arrive until the iMac in August 1998, nearly a year later).  Prior to USB, Macs used different I/O ports than PCs (<a href="https://en.wikipedia.org/wiki/Apple_Desktop_Bus" data-wpel-link="external" target="_blank" rel="external noopener">ADB</a> vs <a href="https://en.wikipedia.org/wiki/PS/2_port" data-wpel-link="external" target="_blank" rel="external noopener">PS/2</a>, for the most part), so there were a lot fewer mouse (and keyboard) options for Macs.  Thus, contextual menus were originally &#8211; and to this day still also &#8211; invoked by control-clicking.  You might have encountered the term &#8220;secondary-click[ing]&#8221;, which arose in that era to abstract over whether it was a physically distinct mouse button or just a modified left click<sup data-fn="0c0ed089-5fc2-434e-8cfb-699487865d79" class="fn"><a href="#0c0ed089-5fc2-434e-8cfb-699487865d79" id="0c0ed089-5fc2-434e-8cfb-699487865d79-link">1</a></sup>.</p>



<p>It <a href="https://www.macobserver.com/tips/how-to/how-to-right-click-on-a-mac/#:~:text=With%20the%20proliferation%20of%20Microsoft%20Windows%2C%20which%20embraced,with%20Mac%20OS%208%2C%20which%20debuted%20in%201997." data-wpel-link="external" target="_blank" rel="external noopener">apparently</a> wasn&#8217;t until the Mighty Mouse in 2005 that Apple actually shipped a multi-button mouse.  (I say apparently because I don&#8217;t personally recall, and I had switched to 3rd party mice long before then anyway so I probably didn&#8217;t even notice at the time)</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>🤔 I don&#8217;t know why SwiftUI gets the terminology wrong, calling it the &#8220;context menu&#8221; rather than contextual menu.  It&#8217;s not just SwiftUI &#8211; <a href="https://developer.apple.com/design/human-interface-guidelines/context-menus" data-wpel-link="external" target="_blank" rel="external noopener">Apple&#8217;s latest Human Interface Guidelines</a> have the same spelling error.</p>



<p>The correct term (in both English and from precedence) is <em>contextual</em>.  The menu provides <em>contextual</em> commands (or context-sensitive, if you prefer).  It is not a &#8220;context&#8221; menu.  That doesn&#8217;t even make sense.  It doesn&#8217;t provide context, it is contextual.</p>
</div></div>



<h2 class="wp-block-heading">Are Services still relevant?</h2>



<p>I assume that the Services submenu &#8211; whether through contextual menus or the app menu &#8211; is not used by most Mac users today, if only because the Mac user-base has expanded massively and most people barely leave their web browsers.  I&#8217;ve heard Services derided or overlooked as a &#8220;power-user&#8221; or &#8220;niche&#8221; feature.  Which is sad, because they can be very handy.</p>



<p>More importantly, some of your app&#8217;s users might rely heavily on Services as part of their personal workflow and choice, and it&#8217;s really not our place &#8211; as native Mac application developers &#8211; to tell them they shouldn&#8217;t use a standard system feature.</p>



<p>It&#8217;s frustrating for end-users to encounter applications which don&#8217;t support standard Mac features, like Services, and makes such applications stand out as non-native or otherwise broken.</p>



<p>It is sad that SwiftUI is in the general company of Electron and its ilk, here.</p>



<h2 class="wp-block-heading">So how&#8217;s it done?</h2>



<p>There&#8217;s <em>probably</em> at least two ways to do this, one of them being to manually insert the Services menu item into an otherwise vanilla SwiftUI menu.  But I quickly ran into non-trivial challenges in pursuing that avenue, as you can&#8217;t just ask AppKit for the Services menu item, or its contents.  Alas.</p>



<p>Ultimately I found it easier &#8211; and more in keeping with the grain &#8211; to instead just not use SwiftUI for contextual menus at all.  But fortunately that doesn&#8217;t mean abandoning SwiftUI entirely, merely intermingling some AppKit into it.</p>



<p>The basic design is the standard AppKit sandwich<sup data-fn="2d6f612a-7fb7-498e-bdd6-68f54de88f78" class="fn"><a href="#2d6f612a-7fb7-498e-bdd6-68f54de88f78" id="2d6f612a-7fb7-498e-bdd6-68f54de88f78-link">2</a></sup>:  an <code><a href="https://developer.apple.com/documentation/swiftui/nsviewrepresentable" data-wpel-link="external" target="_blank" rel="external noopener">NSViewRepresentable</a></code> containing an <code><a href="https://developer.apple.com/documentation/swiftui/nshostingview" data-wpel-link="external" target="_blank" rel="external noopener">NSHostingView</a></code>.</p>



<p>The first part is particularly easy, and just the usual annoying boilerplate:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">ContextualMenuView</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">Content</span><span style="color: #000000">: </span><span style="color: #267F99">View</span><span style="color: #000000">&gt;: NSViewRepresentable {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> viewContent: () -&gt; Content</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> textProvider: </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">init</span><span style="color: #000000">(@</span><span style="color: #795E26">ViewBuilder</span><span style="color: #000000"> </span><span style="color: #001080">viewContent</span><span style="color: #000000">: </span><span style="color: #0000FF">@escaping</span><span style="color: #000000"> () -&gt; Content,</span></span>
<span class="line"><span style="color: #000000">         </span><span style="color: #795E26">text</span><span style="color: #000000">: </span><span style="color: #0000FF">@autoclosure</span><span style="color: #000000"> </span><span style="color: #0000FF">@escaping</span><span style="color: #000000"> </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">viewContent</span><span style="color: #000000"> = viewContent</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">textProvider</span><span style="color: #000000"> = text</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">updateNSView</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">nsView</span><span style="color: #000000">: ContextualMenuViewImplementation&lt;Content&gt;,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">context</span><span style="color: #000000">: NSViewRepresentableContext&lt;ContextualMenuView&gt;) {</span></span>
<span class="line"><span style="color: #000000">        nsView.</span><span style="color: #001080">rootView</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">viewContent</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        nsView.</span><span style="color: #001080">textProvider</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">textProvider</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">makeNSView</span><span style="color: #000000">(</span><span style="color: #795E26">context</span><span style="color: #000000">: Context) -&gt; ContextualMenuViewImplementation&lt;Content&gt; {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">ContextualMenuViewImplementation</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: </span><span style="color: #795E26">viewContent</span><span style="color: #000000">(),</span></span>
<span class="line"><span style="color: #000000">                                         </span><span style="color: #795E26">textProvider</span><span style="color: #000000">: textProvider)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">sizeThatFits</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">proposal</span><span style="color: #000000">: ProposedViewSize,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">nsView</span><span style="color: #000000">: ContextualMenuViewImplementation&lt;Content&gt;,</span></span>
<span class="line"><span style="color: #000000">                      </span><span style="color: #795E26">context</span><span style="color: #000000">: Context) -&gt; CGSize? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> nsView.</span><span style="color: #001080">fittingSize</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>I&#8217;ve hard-coded it for text in this example, for simplicity, but you can adjust that to suite your needs.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ The <code>sizeThatFits(…)</code> implementation is a bit arbitrary.  SwiftUI&#8217;s view sizing methodology is mildly infuriating, in the sense that it&#8217;s both very limited in its capabilities and very confusing for what little it does.  It took me a <em>lot</em> of trial-and-error and reverse engineering to figure out what value I needed to return.  But I suspect it&#8217;s context-sensitive, based on what the subview does.  So feel free to adjust it as necessary for your own use.</p>
</div></div>



<p>For convenience it&#8217;s nice to add a view modifier for this too:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">View</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">contextualMenu</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000"> </span><span style="color: #001080">textProvider</span><span style="color: #000000">: </span><span style="color: #0000FF">@autoclosure</span><span style="color: #000000"> </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000">) -&gt; ContextualMenuView&lt;</span><span style="color: #0000FF">Self</span><span style="color: #000000">&gt; {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">ContextualMenuView</span><span style="color: #000000">(</span><span style="color: #795E26">viewContent</span><span style="color: #000000">: { </span><span style="color: #0000FF">self</span><span style="color: #000000"> },</span></span>
<span class="line"><span style="color: #000000">                           </span><span style="color: #795E26">text</span><span style="color: #000000">: textProvider)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>And now on to the real guts of all this, the custom <code>NSView</code> subclass that will define the contextual menu.  Fortunately, <code>NSView</code> has very straightforward built-in support for contextual menus, so you don&#8217;t need to worry about mouse-event handling &#8211; you just provide it a non-nil <code>NSMenu</code> and it does the rest.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">ContextualMenuViewImplementation</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">Content</span><span style="color: #000000">: </span><span style="color: #267F99">View</span><span style="color: #000000">&gt;: NSHostingView&lt;Content&gt;,</span></span>
<span class="line"><span style="color: #000000">                                                       NSServicesMenuRequestor {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">fileprivate</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> textProvider: </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: Content,</span></span>
<span class="line"><span style="color: #000000">                             </span><span style="color: #795E26">text</span><span style="color: #000000">: </span><span style="color: #0000FF">@autoclosure</span><span style="color: #000000"> </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> () -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">textProvider</span><span style="color: #000000"> = text</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: rootView)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Mandated by NSHostingView, but not actually necessary for our purposes here.  But feel free to give this a real implementation, if that makes sense for your use and needs.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">dynamic</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000"> </span><span style="color: #001080">aDecoder</span><span style="color: #000000">: NSCoder) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">fatalError</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;init(coder:) has not been implemented for ContextualMenuView&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// As above.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">rootView</span><span style="color: #000000">: Content) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">fatalError</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;init(rootView:) has not been implemented for ContextualMenuView&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">validRequestor</span><span style="color: #000000">(</span><span style="color: #795E26">forSendType</span><span style="color: #000000"> </span><span style="color: #001080">sendType</span><span style="color: #000000">: NSPasteboard.PasteboardType?,</span></span>
<span class="line"><span style="color: #000000">                                       </span><span style="color: #795E26">returnType</span><span style="color: #000000">: NSPasteboard.PasteboardType?) -&gt; </span><span style="color: #267F99">Any</span><span style="color: #000000">? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> sendType == .</span><span style="color: #001080">string</span><span style="color: #000000"> || sendType == .</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;NSStringPboardType&quot;</span><span style="color: #000000">) </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">validRequestor</span><span style="color: #000000">(</span><span style="color: #795E26">forSendType</span><span style="color: #000000">: sendType, </span><span style="color: #795E26">returnType</span><span style="color: #000000">: returnType)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">nonisolated</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">writeSelection</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">pboard</span><span style="color: #000000">: NSPasteboard,</span></span>
<span class="line"><span style="color: #000000">                                          </span><span style="color: #795E26">types</span><span style="color: #000000">: [NSPasteboard.PasteboardType]) -&gt; </span><span style="color: #267F99">Bool</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> types.</span><span style="color: #795E26">contains</span><span style="color: #000000">(.</span><span style="color: #001080">string</span><span style="color: #000000">) || types.</span><span style="color: #795E26">contains</span><span style="color: #000000">(.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;NSStringPboardType&quot;</span><span style="color: #000000">)) </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">false</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">        </span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> text = </span><span style="color: #AF00DB">if</span><span style="color: #000000"> Thread.</span><span style="color: #001080">isMainThread</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        } </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">sync</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">                </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        pboard.</span><span style="color: #795E26">setString</span><span style="color: #000000">(text, </span><span style="color: #795E26">forType</span><span style="color: #000000">: .</span><span style="color: #001080">string</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">menu</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000"> </span><span style="color: #001080">event</span><span style="color: #000000">: NSEvent) -&gt; NSMenu? {</span></span>
<span class="line"><span style="color: #000000">        NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #795E26">registerServicesMenuSendTypes</span><span style="color: #000000">([.</span><span style="color: #001080">string</span><span style="color: #000000">, .</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;NSStringPboardType&quot;</span><span style="color: #000000">)],</span></span>
<span class="line"><span style="color: #000000">                                                           </span><span style="color: #795E26">returnTypes</span><span style="color: #000000">: [])</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> menu = </span><span style="color: #795E26">NSMenu</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Insert other menu items here.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> menu</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ <code><a href="https://developer.apple.com/documentation/appkit/nsapplication/1428751-registerservicesmenusendtypes" data-wpel-link="external" target="_blank" rel="external noopener">registerServicesMenuSendTypes(_:returnTypes:)</a></code> is normally called in <code><a href="https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize" data-wpel-link="external" target="_blank" rel="external noopener">+initialize</a></code>, but Swift doesn&#8217;t provide any way to do that (it <em>explicitly</em> bans declaring the method on your <code>NSObject</code> subclasses, for no apparent reason &#8211; perhaps a limitation of Swift&#8217;s Objective-C interoperability).</p>



<p>So, in the example above I&#8217;ve called it (every time!) in <code><a href="https://developer.apple.com/documentation/appkit/nsview/1483231-menu" data-wpel-link="external" target="_blank" rel="external noopener">menu(for:)</a></code>.  That works, but it is inefficient &#8211; you only need to call it once per app session.  If your application has a natural, better place to put it, e.g. during app launch, move it there.</p>
</div></div>



<p>The only real complexity is in the <code><a href="https://developer.apple.com/documentation/appkit/nsservicesmenurequestor/" data-wpel-link="external" target="_blank" rel="external noopener">NSServicesMenuRequestor</a></code> delegate method <code><a href="https://developer.apple.com/documentation/appkit/nsservicesmenurequestor/1428477-writeselection" data-wpel-link="external" target="_blank" rel="external noopener">writeSelection(to:types:)</a></code>.  Because it&#8217;s declared <code>nonisolated</code> by the <code>NSServicesMenuRequestor</code> protocol, you&#8217;re forced to assume it can be called in <em>any</em> isolation context; from any thread.  Unfortunately, at runtime it&#8217;s sometimes &#8211; but not always! &#8211; called from the <em>main</em> thread.  Swift doesn&#8217;t have an elegant way to say &#8220;run this synchronously on the main thread / actor&#8221; &#8211; if you naively call <code><a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/" data-wpel-link="external" target="_blank" rel="external noopener">DispatchQueue</a>.<a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/1781006-main" data-wpel-link="external" target="_blank" rel="external noopener">main</a>.<a href="https://developer.apple.com/documentation/dispatch/dispatchqueue#3119600" data-wpel-link="external" target="_blank" rel="external noopener">sync(…)</a></code> and you&#8217;re <em>already</em> on the main queue, it crashes your application!  So you must manually check for being on the main thread, and handle that specially. 😒</p>



<p>The other thing you might want to consider, not shown in the simple example above, is whether you want to support Services sending data <em>back</em> to your view.  e.g. they might transform the text and return the new text to you, or generate / source text from somewhere else entirely for you.  If you wish to support that, you need to populate the <code>returnTypes</code> argument to <code><a href="https://developer.apple.com/documentation/appkit/nsapplication/1428751-registerservicesmenusendtypes" data-wpel-link="external" target="_blank" rel="external noopener">registerServicesMenuSendTypes(_:returnTypes:)</a></code> <em>and</em> implement the <code><a href="https://developer.apple.com/documentation/appkit/nsservicesmenurequestor/1428481-readselection" data-wpel-link="external" target="_blank" rel="external noopener">readSelection(from:)</a></code> delegate method.</p>



<h2 class="wp-block-heading">How&#8217;s it done <em>better</em>?</h2>



<p>The above works &#8211; and is the canonical way to do it.  It results in the same plain Services submenu as you&#8217;ll see throughout Mac apps.</p>



<p>But that menu&#8217;s not great.  It just dumps everything it a big amorphous list, with no delineation and merely alphabetical ordering.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="334" height="496" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu.webp" alt="Screenshot of the default Services menu for contextual menus, showing a plain list of items ordered alphabetically." class="wp-image-7866" style="width:334px;height:auto" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu.webp 334w, https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu-172x256.webp 172w, https://wadetregaskis.com/wp-content/uploads/2024/03/Dull-Services-submenu@2x.webp 668w" sizes="auto, (max-width: 334px) 100vw, 334px" /></figure>
</div>


<p>Whereas if you look the Services submenu in the application menu:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="442" height="815" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp" alt="Screenshot of the good Services submenu (as found in the application menu), with more options, app icons, and better grouping." class="wp-image-7867" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp 442w, https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu-139x256.webp 139w, https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu@2x.webp 884w, https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu-139x256@2x.webp 278w" sizes="auto, (max-width: 442px) 100vw, 442px" /></figure>
</div>


<p>…it is superior in many ways:</p>



<ul class="wp-block-list">
<li>There are more items.</li>



<li>There&#8217;s a link to System Preferences / Settings to adjust which services are shown.</li>



<li>There&#8217;s dividers in appropriate places, with subheadings, to help visually organise everything.</li>



<li>App icons are shown to better visually distinguish each service.</li>



<li>The grouping is somewhat alphabetical but with all services from a given app shown contiguously.</li>
</ul>



<p>Some of the items shown aren&#8217;t actually context-specific &#8211; or at least, to no more detail than merely which application is targeted &#8211; but they&#8217;re intentionally relegated to the bottom, and could be handy anyway.  e.g. if you can see any right-clickable area of any window of an application, you can quickly start profiling that application in Instruments.</p>



<p>It&#8217;s not <em>hard</em>, per se, to get the better menu.  But it&#8217;s not documented, not <em>officially</em> supported, and has some broken edge cases.  It <em>is</em> how <em>many</em> applications insert the Services menu into their menus (in fact it was <a href="https://github.com/electron/electron/blob/193e162ec6efbb688de2a0fd533c87ab0666d133/shell/browser/ui/cocoa/electron_menu_controller.mm#L332" data-wpel-link="external" target="_blank" rel="external noopener">the Electron source</a> which helped me figure out how to do this in the first place), so it&#8217;s highly unlikely Apple will break it in the foreseeable future.  Nonetheless, be warned.</p>



<p>The main edge case / bug that I&#8217;ve encountered is that this better Services submenu only works if the window is key.  And there&#8217;s no way to <em>force</em> the window to be key (all of the AppKit APIs which seem <em>specifically</em> for that purpose flat-out do not work, such as <code><a href="https://developer.apple.com/documentation/uikit/uiwindow/1621610-makekeywindow" data-wpel-link="external" target="_blank" rel="external noopener">makeKeyWindow</a></code>).  So you have to fall back to the lesser version of the Services menu in those cases.  Which is basically whenever the view&#8217;s window is not the active window when the right-click occurs (or, whenever that macOS bug hits whereby the window is <em>shown</em> as if it&#8217;s the key window but actually isn&#8217;t 😤).</p>



<p>Anyway, with all that in mind and apparently not having dissuaded you, here&#8217;s the code to go in <code>menu(for:)</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> = !(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">window</span><span style="color: #000000">?.</span><span style="color: #001080">isKeyWindow</span><span style="color: #000000"> ?? </span><span style="color: #0000FF">false</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> !menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> &amp;&amp; !(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">window</span><span style="color: #000000">?.</span><span style="color: #795E26">makeFirstResponder</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">) ?? </span><span style="color: #0000FF">false</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Unable to make self the first responder - reverting to built-in Services submenu.&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Add all your other items here.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">if</span><span style="color: #000000"> !menu.</span><span style="color: #001080">allowsContextMenuPlugIns</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(NSMenuItem.</span><span style="color: #795E26">separator</span><span style="color: #000000">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> services = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;Services&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">, </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> serviceSubmenu = </span><span style="color: #795E26">NSMenu</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    services.</span><span style="color: #001080">submenu</span><span style="color: #000000"> = serviceSubmenu</span></span>
<span class="line"><span style="color: #000000">    services.</span><span style="color: #001080">representedObject</span><span style="color: #000000"> = textItem.</span><span style="color: #001080">provider</span></span>
<span class="line"><span style="color: #000000">    NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #001080">servicesMenu</span><span style="color: #000000"> = serviceSubmenu</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(services)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Fortunately &#8211; and thanks to the deep elegance of AppKit&#8217;s design &#8211; that&#8217;s <em>it</em>!  It otherwise uses all the same machinery as before (like <code>writeSelection(to:returnType:)</code>).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ Making the view into the first responder is necessary to ensure it&#8217;s the one that gets called [first] about what data is available to the Services etc.  And it&#8217;s also in principle the correct thing to do &#8211; any view that responds to your interactions should generally be [made] first responder as a result.  And it seems to work perfectly in my use-cases.</p>



<p><em>But</em>, be aware that it <em>could</em> cause issues in some applications, if it interacts poorly with whatever other view(s) lose first responder status as a result.  I can only leave that as a warning and potential exercise for each reader, to figure out how to adapt the above to their circumstances as necessary.</p>



<p>(this isn&#8217;t a concern unique to this code, by any means, more the general warning for whenever you manually change the first responder)</p>
</div></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ macOS 14 Sonoma has a rendering bug when first opening the Services submenu within a given application session, as shown below.  It&#8217;s not a <em>big</em> deal insofar as most of the items still work, and dismissing the menu and re-opening it fixes the rendering.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="367" height="815" src="https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma.webp" alt="Screenshot of the Services menu in macOS 14 Sonoma showing the rendering glitch bug, whereby the bottom third of the menu is mangled, with many items missing entirely and others vertically truncated or displayed atop each other." class="wp-image-7874" srcset="https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma.webp 367w, https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma-115x256.webp 115w, https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma@2x.webp 734w, https://wadetregaskis.com/wp-content/uploads/2024/03/Broken-Services-submenu-rendering-in-macOS-14-Sonoma-115x256@2x.webp 230w" sizes="auto, (max-width: 367px) 100vw, 367px" /></figure>
</div></div></div>



<h2 class="wp-block-heading">Bonuses</h2>



<h3 class="wp-block-heading">Adding a Copy menu item</h3>



<p>Since the contextual menu is empty except for the default Services menu, you&#8217;ll probably want to add in some of the other options that are typically found in the contextual menu &#8211; Copy being perhaps the most prominent and often-used.</p>



<p>Fortunately, it&#8217;s trivial:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">doCopy</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> pb = NSPasteboard.</span><span style="color: #001080">general</span></span>
<span class="line"><span style="color: #000000">    pb.</span><span style="color: #795E26">prepareForNewContents</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    pb.</span><span style="color: #795E26">setString</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">(), </span><span style="color: #795E26">forType</span><span style="color: #000000">: .</span><span style="color: #001080">string</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> copyMenuItem = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;Copy&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">doCopy</span><span style="color: #000000">(_:)), </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    copyMenuItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    copyMenuItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(copyMenuItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>One obvious missing piece is localisation of the &#8220;Copy&#8221; label; left as an exercise for the reader.</p>



<h3 class="wp-block-heading">Adding a Share menu item</h3>



<p>It&#8217;s similarly simple to add a standard Share menu item:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">showSharePopup</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> picker = </span><span style="color: #795E26">NSSharingServicePicker</span><span style="color: #000000">(</span><span style="color: #795E26">items</span><span style="color: #000000">: [</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()])</span></span>
<span class="line"><span style="color: #000000">    picker.</span><span style="color: #795E26">show</span><span style="color: #000000">(</span><span style="color: #795E26">relativeTo</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">bounds</span><span style="color: #000000">, </span><span style="color: #795E26">of</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">, </span><span style="color: #795E26">preferredEdge</span><span style="color: #000000">: .</span><span style="color: #001080">maxY</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> picker = </span><span style="color: #795E26">NSSharingServicePicker</span><span style="color: #000000">(</span><span style="color: #795E26">items</span><span style="color: #000000">: [</span><span style="color: #A31515">&quot;🐞&quot;</span><span style="color: #000000">])</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> pickerMenuItem = picker.</span><span style="color: #001080">standardShareMenuItem</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">action</span><span style="color: #000000"> = </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">showSharePopup</span><span style="color: #000000">(_:))</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(pickerMenuItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This one benefits from using the standard, AppKit-provided menu item, so you don&#8217;t need to handle localising its label.</p>



<p>The 🐞 is there for two reasons:</p>



<ol class="wp-block-list">
<li>You have to provide <code><a href="https://developer.apple.com/documentation/appkit/nssharingservicepicker" data-wpel-link="external" target="_blank" rel="external noopener">NSSharingServicePicker</a></code> the nominal item(s) to share up front, in order to initialise it (and so it can tailor its display to the content).  <code><a href="https://developer.apple.com/documentation/appkit/nssharingservicepicker/4031316-standardsharemenuitem" data-wpel-link="external" target="_blank" rel="external noopener">standardShareMenuItem</a></code> should actually be a class member variable, not an instance member variable &#8211; a design flaw in AppKit.<br><br>You don&#8217;t want to invoke the <code>textProvider</code> closure unless you really need to (it could be time-consuming to run, so you don&#8217;t want to run it unnecessarily nor while the user is trying to navigate the contextual menu lest it cause GUI hiccups).  So an equivalent placeholder value &#8211; any other string, in this case &#8211; is better, and suffices.</li>



<li>I use the ladybug emoji so that it stands out if the value ever somehow gets shown to the user (it&#8217;s a bug, get it? 😄).</li>
</ol>



<h3 class="wp-block-heading">Adding a Look Up menu item</h3>



<p>This one&#8217;s a little bit more iffy.  If you think you genuinely have need of a Look Up item, consider whether you should be instead making the text selectable and simply utilising the built-in contextual menu support that selectable text views have in SwiftUI.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">lookUp</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">showDefinition</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: </span><span style="color: #795E26">NSAttributedString</span><span style="color: #000000">(</span><span style="color: #795E26">string</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()),</span></span>
<span class="line"><span style="color: #000000">                        </span><span style="color: #795E26">at</span><span style="color: #000000">: </span><span style="color: #795E26">NSPoint</span><span style="color: #000000">(</span><span style="color: #795E26">x</span><span style="color: #000000">: CGFloat.</span><span style="color: #001080">infinity</span><span style="color: #000000">, </span><span style="color: #795E26">y</span><span style="color: #000000">: CGFloat.</span><span style="color: #001080">infinity</span><span style="color: #000000">))</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> lookupItem = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: submenu == menu ? </span><span style="color: #A31515">&quot;Look Up “</span><span style="color: #0000FF">\(self</span><span style="color: #000000FF">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000FF">()</span><span style="color: #0000FF">)</span><span style="color: #A31515">”&quot;</span><span style="color: #000000"> : </span><span style="color: #A31515">&quot;Look Up&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">lookUp</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    lookupItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    lookupItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(lookupItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This doesn&#8217;t display perfectly.  The use of infinities for the <code>at</code> argument causes it to draw a small yellow box at the top left of the view, layered underneath the contextual menu but usually still visible.  That&#8217;s a hack for cases where you can&#8217;t determine where the text is, or the text being looked up doesn&#8217;t exactly match what&#8217;s shown in the view.</p>



<p>If you know the <em>actual</em> location of the selected text, you can specify that instead to get the yellow text drawn in that location.  But beware: the text from <code>textProvider</code> must <em>exactly</em> match what&#8217;s rendered in the view, otherwise the yellow overlaid box &#8211; intended to look like a highlight effect &#8211; will look weird, because it [re]renders the text based on what <code>textProvider</code> returned.  It also might not correctly match the font, in any case.</p>



<p>Thus why I caution against using the reinvention of this particular wheel.</p>


<ol class="wp-block-footnotes"><li id="0c0ed089-5fc2-434e-8cfb-699487865d79">Possibly borrowed from earlier computers that had multi-mouse-button support, such as some Unixes, but I suspect just coincidence.  I vaguely recall that they were the philosophical antithesis of Apple w.r.t. mouse buttons, with some *nix GUIs <em>requiring</em> at least three mouse buttons for their basic function.  I seem to recall some of them labelling the buttons primary, secondary, and tertiary. <a href="#0c0ed089-5fc2-434e-8cfb-699487865d79-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="2d6f612a-7fb7-498e-bdd6-68f54de88f78">I doubt I came up with this metaphor &#8211; although it&#8217;s pretty obvious in any case &#8211; but it&#8217;s worth considering if it&#8217;s more than just a cute superficial analogy.  AppKit forms the meaningful contents of the sandwich, providing its flavour, substance, and value, while SwiftUI serves only as the bread, there mainly just to convey the contents. 🤔 <a href="#2d6f612a-7fb7-498e-bdd6-68f54de88f78-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7861</post-id>	</item>
		<item>
		<title>no platform load command found in &#8216;libxyz.a&#8217;, assuming: macOS</title>
		<link>https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/</link>
					<comments>https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 14 Mar 2024 23:36:16 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[LC_BUILD_VERSION]]></category>
		<category><![CDATA[ld]]></category>
		<category><![CDATA[ld_prime]]></category>
		<category><![CDATA[ld-classic]]></category>
		<category><![CDATA[ld64]]></category>
		<category><![CDATA[linker load commands]]></category>
		<category><![CDATA[NASM]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7856</guid>

					<description><![CDATA[This is a linker warning I see frequently since Xcode 15.0. It appears it&#8217;s a result of Apple&#8217;s new linker, &#8220;ld_prime&#8221;, which replaced &#8220;ld64&#8221; that was in use [by Apple] since around 2005 (per Quinn the Eskimo). ☝️ &#8220;ld_prime&#8221; might be an internal code name, or perhaps is just Quinn&#8217;s personal nomenclature. The actual binary&#8230; <a class="read-more-link" href="https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is a linker warning I see frequently <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#New-Features" data-wpel-link="external" target="_blank" rel="external noopener">since Xcode 15.0</a>.  It appears it&#8217;s a result of Apple&#8217;s new linker, &#8220;ld_prime&#8221;, which replaced &#8220;ld64&#8221; that was in use [by Apple] since around 2005 (<a href="https://forums.developer.apple.com/forums/thread/715385" data-wpel-link="external" target="_blank" rel="external noopener">per Quinn the Eskimo</a>).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ &#8220;ld_prime&#8221; might be an internal code name, or perhaps is just Quinn&#8217;s personal nomenclature.  The actual binary and project name is simply <code>ld</code> (not to be confused with the <em>original</em> <code>ld</code>, the <em>old</em> old linker, that Apple used until <code>ld64</code> replaced it in 2005).</p>



<p>The old linker &#8211; &#8220;ld64&#8221; &#8211; was retroactively renamed <code>ld-classic</code>.  They emit <em>almost</em> identical version strings, making them easy to confuse at a glance, but for subtly different program and project names:</p>



<pre class="wp-block-preformatted"><strong>👾 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld -v
</strong>@(#)PROGRAM:<strong>ld</strong> PROJECT:<strong>ld</strong>-1053.12
BUILD 15:44:24 Feb  3 2024
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em
will use ld-classic for: armv6 armv7 armv7s arm64_32 i386 armv6m armv7k armv7m armv7em
LTO support using: LLVM version 15.0.0 (static support for 29, runtime is 29)
TAPI support using: Apple TAPI version 15.0.0 (tapi-1500.3.2.2)

<strong>👾 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic -v</strong>
@(#)PROGRAM:<strong>ld-classic</strong>  PROJECT:<strong>ld64</strong>-951.9
BUILD 15:44:42 Feb  3 2024
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em
LTO support using: LLVM version 15.0.0 (static support for 29, runtime is 29)
TAPI support using: Apple TAPI version 15.0.0 (tapi-1500.3.2.2)</pre>



<p>Tangentially, I believe the similarity of their version output is entirely intentional, as a lot of programs (particularly within build systems like CMake) make assumptions about the output, for better or worse.</p>
</div></div>



<p>Apple&#8217;s new linker appears to be much more pedantic than the old one &#8211; it warns about a lot of things that the old one didn&#8217;t care about.  One of these is missing platform load commands:</p>



<pre class="wp-block-preformatted">/Users/SadPanda/Documents/vmaf/libvmaf/ld:1:1: no platform load command found in 'src/libvmaf.a[62](cpuid.obj)', assuming: macOS</pre>



<p>This doesn&#8217;t technically break anything &#8211; assuming it guessed the platform correctly, which I suspect it just takes as being the host&#8217;s platform &#8211; but it&#8217;s super annoying because it&#8217;s emitted for every afflicted object file the linker sees (that&#8217;s individual files, even if they&#8217;re buried in archive files &#8211; e.g. libfoo.a).  You can have hundreds or even thousands of these warnings for a single library.  Worse, they&#8217;re emitted when you link against the library, not just when you build it.  And with nested static libraries they can propagate up a build chain endlessly.</p>



<p>If we look at the offending file with <code>otool</code>, we see that indeed it has no platform load command:</p>



<pre class="wp-block-preformatted"><strong>👾 otool -vl cpuid.obj</strong>
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 152
  segname 
   vmaddr 0x0000000000000000
   vmsize 0x000000000000002d
  fileoff 208
 filesize 45
  maxprot rwx
 initprot rwx
   nsects 1
    flags (none)
Section
  sectname __text
   segname __TEXT
      addr 0x0000000000000000
      size 0x000000000000002d
    offset 208
     align 2^4 (16)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0
Load command 1
     cmd LC_SYMTAB
 cmdsize 24
  symoff 256
   nsyms 4
  stroff 320
 strsize 56</pre>



<p>For reference, here&#8217;s an example of the load command it&#8217;s looking for:</p>



<pre class="wp-block-preformatted">Load command 1
      cmd LC_BUILD_VERSION
  cmdsize 24
 platform MACOS
    minos 14.2
      sdk 14.4
   ntools 0</pre>



<h2 class="wp-block-heading">Why is it missing?</h2>



<p>There&#8217;s likely many reasons for this, but they all come down to the same general reason:  something is wrong with one [or more] of the tools in the toolchain that created the object file.  All object files <em>should</em> contain this load command, on Apple platforms.  Though the linker currently treats it as optional, the warning is likely a hint from Apple that it may become required in future.  More importantly, without it the linker cannot know which SDK the file is compatible with, and therefore cannot validate that it&#8217;s linking the right things.</p>



<p>In the example case above, the problem tool is <code><a href="https://www.nasm.us" data-wpel-link="external" target="_blank" rel="external noopener">nasm</a></code>.  It&#8217;s an open-source, non-Apple x86-family <a href="https://en.wikipedia.org/wiki/Assembly_language#Assembler" data-wpel-link="external" target="_blank" rel="external noopener">assembler</a>.  It&#8217;s been around for nearly thirty years &#8211; its first platform was DOS.  Though it supports Apple&#8217;s platforms, it is predominately focused on Linux &amp; Windows.  So it&#8217;s not entirely surprising that it&#8217;s causing issues.</p>



<p>It also hasn&#8217;t released even a minor patch update since 2022 (with version 2.16.01), so nominally it predates and doesn&#8217;t support Xcode 15 (announced &amp; released mid-2023).</p>



<p>However, the absence of the <code>LC_BUILD_VERSION</code> command was in fact a problem even before the new Apple linker in Xcode 15, because of cross-compiling (e.g. targeting the iOS simulator on a Mac).  There was even a patch to address the problem, <a href="https://lists.nasm.us/archives/nasm-devel/2021-July/000035.html" data-wpel-link="external" target="_blank" rel="external noopener">submitted by Byoungchan Lee way back in 2021</a>, which was never accepted (it seems to have been completely ignored &#8211; not even a response on the mailing list).</p>



<h2 class="wp-block-heading">Workarounds?</h2>



<p>No good ones, that I can find.  Stoically ignoring the warning for now is probably the best option.</p>



<p>It is still possible (in Xcode 15, at least) to switch to using the old linker instead, using the <code>-Wl,-ld_classic</code> flags, although that&#8217;s not a long-term solution and may have downsides (e.g. some folks report significantly smaller object files with the new linker).</p>



<p>It&#8217;s <em>probably</em> possible to manually insert the missing load command &#8211; if your build process is amenable to that &#8211; although it&#8217;s not apparent to me what tool or command invocation you would use to do that.  Possibly <code>ld</code> itself, though its documentation provides no insight into this.</p>



<p>For some projects, you might be able to switch to a different assembler (e.g. Apple&#8217;s native <code>as</code> from Clang, or <a href="https://yasm.tortall.net" data-wpel-link="external" target="_blank" rel="external noopener">Yasm</a>, etc).  Apple&#8217;s toolchains don&#8217;t have this problem (so far as I&#8217;ve seen) but are of course not available on other platforms (though, being based on Clang, you can probably get essentially the same thing direct from <a href="https://clang.llvm.org" data-wpel-link="external" target="_blank" rel="external noopener">Clang</a>).  I haven&#8217;t tested whether Yasm has the issue.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7856</post-id>	</item>
		<item>
		<title>The privileges &#038; challenges of being a primitive type for Codable in Swift</title>
		<link>https://wadetregaskis.com/the-privileges-challenges-of-being-a-primitive-type-for-codable-in-swift/</link>
					<comments>https://wadetregaskis.com/the-privileges-challenges-of-being-a-primitive-type-for-codable-in-swift/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 14 Mar 2024 00:29:49 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Codable]]></category>
		<category><![CDATA[Encodable]]></category>
		<category><![CDATA[JSONEncoder]]></category>
		<category><![CDATA[SE-425]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[UInt128]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7849</guid>

					<description><![CDATA[This is an elaboration of some posts made in the discussion of SE-425: 128-bit Integer Types. I think it warrants sharing a little more broadly &#8211; not because most people ever need to care about this, but rather because it&#8217;s interesting. You might have noticed that the primitive types e.g.: …all conform to Codable (which&#8230; <a class="read-more-link" href="https://wadetregaskis.com/the-privileges-challenges-of-being-a-primitive-type-for-codable-in-swift/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is an elaboration of some posts made in <a href="https://forums.swift.org/t/se-0425-128-bit-integer-types/70456" data-wpel-link="external" target="_blank" rel="external noopener">the discussion of</a> <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0425-int128.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-425: 128-bit Integer Types</a>.  I think it warrants sharing a little more broadly &#8211; not because most people ever need to care about this, but rather because it&#8217;s interesting.</p>



<p>You might have noticed that the primitive types e.g.:</p>



<ul class="wp-block-list">
<li><code>Bool</code></li>



<li><code>Int</code> &amp; <code>UInt</code> (and all fixed-sized variants, e.g. <code>Int8</code>)</li>



<li><code>String</code></li>



<li><code>Float</code></li>



<li><code>Double</code></li>



<li><code>Nil</code></li>
</ul>



<p>…all conform to <code><a href="https://developer.apple.com/documentation/swift/codable" data-wpel-link="external" target="_blank" rel="external noopener">Codable</a></code> (which is just a convenience protocol combining <code><a href="https://developer.apple.com/documentation/swift/encodable" data-wpel-link="external" target="_blank" rel="external noopener">Encodable</a></code> &amp; <code><a href="https://developer.apple.com/documentation/swift/decodable" data-wpel-link="external" target="_blank" rel="external noopener">Decodable</a></code>).  That means they have two methods:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">encode</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">encoder</span><span style="color: #000000">: any Encoder) </span><span style="color: #AF00DB">throws</span></span>
<span class="line"><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">from</span><span style="color: #000000"> </span><span style="color: #001080">decoder</span><span style="color: #000000">: any Decoder) </span><span style="color: #AF00DB">throws</span></span></code></pre></div>



<p>This is good &#8211; it ensures you can reason about <code>Encodable</code> and <code>Decodable</code> types and not have any weird exceptions for these primitives, e.g. an <code>Array&lt;MyCodableStruct&gt;</code> is just as <code>Codable</code> as <code>Array&lt;String&gt;</code>.  <code>Array</code> doesn&#8217;t have to special-case the primitive types, it just calls <code>encode</code> on its <code>Element</code>s irrespectively.</p>



<p>However, if you stop and think about it, it might seem like there&#8217;s an infinite loop here.  For example, if you call <code>encode(to:)</code> on <code>UInt64</code>, it has only the <code><a href="https://developer.apple.com/documentation/swift/encoder" data-wpel-link="external" target="_blank" rel="external noopener">Encoder</a></code> APIs to work with &#8211; it <em>has</em> to use those APIs, because it doesn&#8217;t&nbsp;<em>actually</em>&nbsp;know how to serialise itself, because it has no idea what the serialisation format is &#8211; that&#8217;s defined by the particular&nbsp;<code>Encoder</code>&nbsp;/&nbsp;<code>Decoder</code>&nbsp;in use.</p>



<p>So, basically <code>UInt64</code> has to call an <code>encode</code> method somewhere, like <a href="https://developer.apple.com/documentation/swift/singlevalueencodingcontainer/encode(_:)-687yj" data-wpel-link="external" target="_blank" rel="external noopener">this one</a>, which would surely then just call <code>encode(to: self)</code> on the original <code>UInt64</code>, right?  Infinite recursion!</p>



<p>Of course, no, thankfully.</p>



<p>The first part of that thinking is correct &#8211; see for example <a href="https://github.com/apple/swift/blob/675fda3a4bb2d0a9693a43548de60901dbdfc0e1/stdlib/public/core/Codable.swift#L5308" data-wpel-link="external" target="_blank" rel="external noopener"><code>UInt64</code>s actual implementation of <code>Codable</code></a>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">UInt64</span><span style="color: #000000">: Codable {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">public</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">from</span><span style="color: #000000"> </span><span style="color: #001080">decoder</span><span style="color: #000000">: any Decoder) </span><span style="color: #AF00DB">throws</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000"> = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> decoder.</span><span style="color: #795E26">singleValueContainer</span><span style="color: #000000">().</span><span style="color: #795E26">decode</span><span style="color: #000000">(</span><span style="color: #267F99">UInt64</span><span style="color: #000000">.self)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">public</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">encode</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">encoder</span><span style="color: #000000">: any Encoder) </span><span style="color: #AF00DB">throws</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> container = encoder.</span><span style="color: #795E26">singleValueContainer</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">try</span><span style="color: #000000"> container.</span><span style="color: #795E26">encode</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>The trick to avoiding self-recursion is that encoders &amp; decoders <em>must special-case the primitive types</em>.  They may <em>never</em> call <code>encode(to: self)</code> / <code>init(from: self)</code> on the primitive types.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>😔 Alas the compiler does not enforce this &#8211; if this rule is broken, the consequence is indeed infinite recursion at runtime.</p>
</div></div>



<p>The way they do this is two-fold:</p>



<ol class="wp-block-list">
<li>There are specific <a href="https://developer.apple.com/documentation/swift/singlevalueencodingcontainer" data-wpel-link="external" target="_blank" rel="external noopener">overloads of <code>encode</code></a> (and <code><a href="https://developer.apple.com/documentation/swift/singlevaluedecodingcontainer" data-wpel-link="external" target="_blank" rel="external noopener">decode</a></code>) for the primitive types.<br><br>An encoder / decoder doesn&#8217;t <em>technically</em> have to provide those specialised overloads &#8211; it can just implement the generic version, which will satisfy the protocol constraints &#8211; but this is ultimately only an optimisation because either way it <em>must</em> serialise those primitive values <em>intrinsically</em>…</li>



<li>The implementation of the generic methods <em>must</em> special-case the primitive types.  e.g. <a href="https://github.com/apple/swift-foundation/blob/69f473d8879ec494b4bcc5f97d091c2b5d263acb/Sources/FoundationEssentials/JSON/JSONEncoder.swift#L1112" data-wpel-link="external" target="_blank" rel="external noopener">the pertinent bit of <code>JSONEncoder</code>&#8216;s implementation</a>:</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">wrapGeneric</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">T</span><span style="color: #000000">: </span><span style="color: #267F99">Encodable</span><span style="color: #000000">&gt;(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">value</span><span style="color: #000000">: T, </span><span style="color: #795E26">for</span><span style="color: #000000"> </span><span style="color: #001080">node</span><span style="color: #000000">: _CodingPathNode, </span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">additionalKey</span><span style="color: #000000">: (some CodingKey)? = _CodingKey?.</span><span style="color: #001080">none</span><span style="color: #000000">) </span><span style="color: #AF00DB">throws</span><span style="color: #000000"> -&gt; JSONReference? {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">switch</span><span style="color: #000000"> T.self {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">case</span><span style="color: #000000"> is Date.Type:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Respect Date encoding strategy</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">wrap</span><span style="color: #000000">(value as! Date, </span><span style="color: #795E26">for</span><span style="color: #000000">: node, additionalKey)</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">case</span><span style="color: #000000"> is Data.Type:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Respect Data encoding strategy</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">wrap</span><span style="color: #000000">(value as! Data, </span><span style="color: #795E26">for</span><span style="color: #000000">: node, additionalKey)</span></span>
<span class="line"><span style="color: #0000FF">#</span><span style="color: #AF00DB">if</span><span style="color: #0000FF"> FOUNDATION_FRAMEWORK</span><span style="color: #000000"> </span><span style="color: #008000">// TODO: Reenable once URL and Decimal are moved</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">case</span><span style="color: #000000"> is URL.Type:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Encode URLs as single strings.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> url = value as! URL</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">wrap</span><span style="color: #000000">(url.</span><span style="color: #001080">absoluteString</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">case</span><span style="color: #000000"> is Decimal.Type:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> decimal = value as! Decimal</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> .</span><span style="color: #795E26">number</span><span style="color: #000000">(decimal.</span><span style="color: #001080">description</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #0000FF">#</span><span style="color: #AF00DB">endif</span><span style="color: #0000FF"> </span><span style="color: #008000">// FOUNDATION_FRAMEWORK</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">case</span><span style="color: #000000"> is _JSONStringDictionaryEncodableMarker.Type:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">wrap</span><span style="color: #000000">(value as! [</span><span style="color: #267F99">String</span><span style="color: #000000"> : Encodable], </span><span style="color: #795E26">for</span><span style="color: #000000">: node, additionalKey)</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">case</span><span style="color: #000000"> is _JSONDirectArrayEncodable.Type:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> array = value as! _JSONDirectArrayEncodable</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">if</span><span style="color: #000000"> options.</span><span style="color: #001080">outputFormatting</span><span style="color: #000000">.</span><span style="color: #795E26">contains</span><span style="color: #000000">(.</span><span style="color: #001080">prettyPrinted</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> .</span><span style="color: #0000FF">init</span><span style="color: #000000">(.</span><span style="color: #795E26">directArray</span><span style="color: #000000">(array.</span><span style="color: #795E26">individualElementRepresentation</span><span style="color: #000000">(</span><span style="color: #795E26">options</span><span style="color: #000000">: options)))</span></span>
<span class="line"><span style="color: #000000">        } </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> .</span><span style="color: #0000FF">init</span><span style="color: #000000">(.</span><span style="color: #795E26">nonPrettyDirectArray</span><span style="color: #000000">(array.</span><span style="color: #795E26">nonPrettyJSONRepresentation</span><span style="color: #000000">(</span><span style="color: #795E26">options</span><span style="color: #000000">: options)))</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">default</span><span style="color: #000000">:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">break</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #795E26">_wrapGeneric</span><span style="color: #000000">({</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">try</span><span style="color: #000000"> value.</span><span style="color: #795E26">encode</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000">: </span><span style="color: #0000FF">$0</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }, </span><span style="color: #795E26">for</span><span style="color: #000000">: node, additionalKey)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This specialisation <em>inside the generic method</em> is required even if there are specialised overloads, because the type of <code>T</code> is not always known at runtime; sometimes it truly is an <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/opaquetypes/" data-wpel-link="external" target="_blank" rel="external noopener">existential</a>.  So the specialisations can&#8217;t always be called directly.  In the case of <code>JSONEncoder</code> (above) it <em>manually</em> unboxes the existential and invokes the appropriate specialisation.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Tangentially, notice how <code>JSONEncoder</code> has special-case handling of <code>Date</code>, <code>Data</code>, and <code>Dict</code>, even though those types are not considered &#8216;primitives&#8217; and do have their own, fully-functional <code>Codable</code> implementations in terms of the primitive types (e.g. <a href="https://github.com/apple/swift-foundation/blob/69f473d8879ec494b4bcc5f97d091c2b5d263acb/Sources/FoundationEssentials/Data/Data.swift#L2756" data-wpel-link="external" target="_blank" rel="external noopener"><code>Data</code></a>&#8216;s).  This is because <code>JSONEncoder</code> believes it can do a better job for those types, given its specific knowledge of the JSON format.  Encoders &amp; decoders are always allowed to specialise <em>additional</em> types beyond the primitive types.</p>
</div></div>



<p>For all types other than those primitive types, their <code>Codable</code> representation must ultimately be defined in terms of <em>only</em> those primitive types (with allowances for keyed containers (a la <code>Dictionary</code>) and unkeyed containers (a la <code>Array</code>) to provide structure).</p>



<p>Consider for example <a href="https://github.com/apple/swift/blob/675fda3a4bb2d0a9693a43548de60901dbdfc0e1/stdlib/public/core/Codable.swift#L5372" data-wpel-link="external" target="_blank" rel="external noopener"><code>Optional</code>&#8216;s <code>Codable</code> conformance</a>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">public</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">encode</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">encoder</span><span style="color: #000000">: any Encoder) </span><span style="color: #AF00DB">throws</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> container = encoder.</span><span style="color: #795E26">singleValueContainer</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">switch</span><span style="color: #000000"> </span><span style="color: #0000FF">self</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">case</span><span style="color: #000000"> .</span><span style="color: #001080">none</span><span style="color: #000000">: </span><span style="color: #AF00DB">try</span><span style="color: #000000"> container.</span><span style="color: #795E26">encodeNil</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">case</span><span style="color: #000000"> .</span><span style="color: #001080">some</span><span style="color: #000000">(</span><span style="color: #0000FF">let</span><span style="color: #000000"> wrapped): </span><span style="color: #AF00DB">try</span><span style="color: #000000"> container.</span><span style="color: #795E26">encode</span><span style="color: #000000">(wrapped)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">public</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">from</span><span style="color: #000000"> </span><span style="color: #001080">decoder</span><span style="color: #000000">: any Decoder) </span><span style="color: #AF00DB">throws</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">let</span><span style="color: #000000"> container = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> decoder.</span><span style="color: #795E26">singleValueContainer</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">if</span><span style="color: #000000"> container.</span><span style="color: #795E26">decodeNil</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000"> = .</span><span style="color: #001080">none</span></span>
<span class="line"><span style="color: #000000">  }  </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> element = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> container.</span><span style="color: #795E26">decode</span><span style="color: #000000">(Wrapped.self)</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000"> = .</span><span style="color: #001080">some</span><span style="color: #000000">(element)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>It doesn&#8217;t have the luxury of a special-case guaranteed by all encoders &amp; decoders, so it has to figure out how to represent itself using only the primitive types.</p>



<p>It demonstrates <em>both</em> ways of doing this:  direct use of primitive types, and indirect (a.k.a. punting the problem downwards).</p>



<p>If the <code>Optional</code> is empty, it encodes itself as simply a <code>nil</code> value (one of the supported primitive types).</p>



<p>If it&#8217;s not empty, it simply defers to the wrapped value to do the work.  That value must then do the same thing &#8211; figure out a way to represent itself using only the primitive types and/or punt the challenge to its component types.</p>



<p>So if you have e.g. <code>Optional&lt;Int64></code>, that essentially looks like:</p>



<ol class="wp-block-list">
<li><code><a href="https://github.com/apple/swift/blob/675fda3a4bb2d0a9693a43548de60901dbdfc0e1/stdlib/public/core/Codable.swift#L5379" data-wpel-link="external" target="_blank" rel="external noopener">Optional.encode(to: someJSONEncoder)</a></code> which just calls…</li>



<li><code><a href="https://github.com/apple/swift/blob/675fda3a4bb2d0a9693a43548de60901dbdfc0e1/stdlib/public/core/Codable.swift#L5025" data-wpel-link="external" target="_blank" rel="external noopener">Int64.encode(to: someJSONEncoder)</a></code> which just calls…</li>



<li><code><a href="https://github.com/apple/swift-foundation/blob/69f473d8879ec494b4bcc5f97d091c2b5d263acb/Sources/FoundationEssentials/JSON/JSONEncoder.swift#L907" data-wpel-link="external" target="_blank" rel="external noopener">someJSONEncoder.singleValueContainer().encode(self)</a></code> (where self is the <code>Int64</code> value), which has some further redirection through abstractions but ultimately does the <em>actual</em> serialisation.</li>
</ol>



<h2 class="wp-block-heading">Can new primitive types be added?</h2>



<p>That is in fact what prompted this post and the discussion that precipitated it.  <code>Int128</code> &amp; <code>UInt128</code> or finally coming to Swift (properly &#8211; the standard library has had them internally for a while).  But that raises the question of how they will be supported for <code>Codable</code>.  Technically, the three options are:</p>



<ol class="wp-block-list">
<li>Be added to the Codable system as primitive types (i.e. additional overloads for them on <a href="https://developer.apple.com/documentation/swift/singlevalueencodingcontainer" data-wpel-link="external" target="_blank" rel="external noopener">SingleValueEncodingContainer</a> &amp; friends).</li>



<li><em>Not</em> be added as <code>Codable</code>-recognised primitive types, and instead implement their <code>Codable</code> conformance in terms of only the existing primitive types, e.g. as strings instead, or pairs of 64-bit integers, etc.</li>



<li>Not support <code>Codable</code> at all.</li>
</ol>



<p>Option #3 is obviously <em>highly</em> undesirable.  Note that if the standard library doesn&#8217;t provide <code>Codable</code> conformance for these types, <em>no-one can</em>, because protocol conformances cannot be added outside of the modules which define at least one of (a) the type in question or (b) the protocol in question.  Since the standard library defines both in this case, it is the <em>only</em> place where the conformance may be made.</p>



<p>Option #2 is the only other option most types have; most don&#8217;t have the luxury of getting special treatment from encoders &amp; decoders like the standard library&#8217;s primitive types do.  But it&#8217;s a frustrating option for <code>Int128</code> &amp; <code>UInt128</code> because it adds runtime and possibly wire-format overhead to their use as <code>Codable</code>, and makes their serialisation &amp; deserialisation more complicated.</p>



<p>Interestingly, it does <em>not</em> preclude them from being supported more efficiently &amp; elegantly by encoders &amp; decoders that do intrinsically supported them because, as we saw with <code>JSONEncoder</code>, the encoder &amp; decoder are always free to <em>override</em> the standard representation for any type.</p>



<p>Option #1 seems simple and obviously appropriate for these types that are, after all, just new siblings into the <code>FixedWidthInteger</code> standard library family.  However, adding new requirements (properties, methods, etc) to a protocol is a <em>breaking change</em>; it is both source-incompatible and binary-incompatible.  …<em>unless</em> the new requirements come with default implementations.  The problem is, what would the default implementation be?</p>



<p>There are technically three sub-options:</p>



<ol class="wp-block-list">
<li>Have the default implementation be effectively the same as Option #2 above; implemented in terms of the <em>existing</em> primitive types.</li>



<li>Throw an exception (fortunately all the relevant encoding &amp; decoding methods are already marked <code>throws</code> with no restriction on the thrown type, because they predate <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md" data-wpel-link="external" target="_blank" rel="external noopener">typed throws</a>).</li>



<li>Crash (or hang, or similar).</li>
</ol>



<p>At time of writing the debate is ongoing as to whether sub-option #1 or #2 is the best option for <code>Int128</code> &amp; <code>UInt128</code>.  Personally I think throwing an exception is the best option (for reasons <a href="https://forums.swift.org/t/se-0425-128-bit-integer-types/70456/35" data-wpel-link="external" target="_blank" rel="external noopener">detailed in the forum thread</a>), but only time will tell what Swift ultimately chooses.</p>



<p>Either way, the important thing is for encoders &amp; decoders to <em>actually</em> add support for the new primitives.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/the-privileges-challenges-of-being-a-primitive-type-for-codable-in-swift/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7849</post-id>	</item>
		<item>
		<title>Another Swift release, another day wasted</title>
		<link>https://wadetregaskis.com/another-swift-release-another-day-wasted/</link>
					<comments>https://wadetregaskis.com/another-swift-release-another-day-wasted/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 06 Mar 2024 22:32:35 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7838</guid>

					<description><![CDATA[I really wish Swift releases would stop having major regressions in the ability to parse non-trivial expressions. The installation of a new version of Xcode &#8211; with a corresponding new Swift version &#38; toolchain &#8211; should be a joyous occasion, not one I increasingly dread.]]></description>
										<content:encoded><![CDATA[
<p>I really wish Swift releases would stop having major regressions in the ability to parse non-trivial expressions.  The installation of a new version of Xcode &#8211; with a corresponding new Swift version &amp; toolchain &#8211; should be a <em>joyous</em> occasion, not one I increasingly dread.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/another-swift-release-another-day-wasted/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/03/Sad-Keanu.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7838</post-id>	</item>
	</channel>
</rss>
