<?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>Swift &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/swift/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Tue, 17 Mar 2026 01:43:51 +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>Swift &#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>&#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>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 fetchpriority="high" 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>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>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 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="(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 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="(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>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>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>
		<item>
		<title>Beware of specifying isolation requirements for whole protocols</title>
		<link>https://wadetregaskis.com/beware-of-specifying-isolation-requirements-for-whole-protocols/</link>
					<comments>https://wadetregaskis.com/beware-of-specifying-isolation-requirements-for-whole-protocols/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 01 Mar 2024 19:55:32 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Actor]]></category>
		<category><![CDATA[isolation]]></category>
		<category><![CDATA[protocol]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7829</guid>

					<description><![CDATA[Matt Massicotte has a well-written, brief introduction to isolation in Swift. But it mostly just enumerates the state of things, without offering much guidance. One pitfall in particular is important to call out, regarding isolated protocols. These might seem pretty similar &#8211; you&#8217;d be forgiven for assuming it&#8217;s just a convenience to put @MainActor on&#8230; <a class="read-more-link" href="https://wadetregaskis.com/beware-of-specifying-isolation-requirements-for-whole-protocols/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><a href="https://www.massicotte.org/about" data-wpel-link="external" target="_blank" rel="external noopener">Matt Massicotte</a> has <a href="https://www.massicotte.org/intro-to-isolation" data-wpel-link="external" target="_blank" rel="external noopener">a well-written, brief introduction to isolation in Swift</a>.  But it mostly just enumerates the state of things, without offering much guidance.  One pitfall in particular is important to call out, regarding isolated protocols.</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: #001080">MainActor</span></span>
<span class="line"><span style="color: #001080">protocol</span><span style="color: #000000"> </span><span style="color: #001080">GloballyIsolatedProtocol</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</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: #001080">protocol</span><span style="color: #000000"> </span><span style="color: #001080">PerMemberIsolatedProtocol</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    @</span><span style="color: #001080">MainActor</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>These might seem pretty similar &#8211; you&#8217;d be forgiven for assuming it&#8217;s just a convenience to put <code>@MainActor</code> on the protocol overall rather than having to repeat it for every member of the protocol.  Less error-prone, too.</p>



<p>But, you generally shouldn&#8217;t do that.  They are <em>not</em> equivalent.</p>



<p>The first form is not merely saying that all the members of the protocol require a certain isolation, but that the <em>type</em> that conforms to the protocol must have that isolation.  The <em>whole</em> type.</p>



<p>And you might think:  …so what?</p>



<p>And indeed it <em>seems</em> like it&#8217;s fine:</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: #001080">MainActor</span></span>
<span class="line"><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">doStuff</span><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">ClassA</span><span style="color: #000000">: </span><span style="color: #267F99">GloballyIsolatedProtocol</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">doStuff</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: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">ClassB</span><span style="color: #000000">: </span><span style="color: #267F99">PerMemberIsolatedProtocol</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">doStuff</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: #008000">// ✅ Everything is hunky-dory as far as the compiler is concerned.</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>🤔 Some folks might think it&#8217;s a bit dangerously magical that <code>ClassA</code> is secretly <code>@MainActor</code> now by simply conforming to the protocol, even though nothing in the class&#8217;s declaration actually says that &#8211; and likewise for <code>method</code> for <code>ClassB</code>.  That&#8217;s largely a separate topic.  But okay, it makes a <em>kind</em> of sense at least…</p>
</div></div>



<p>But try to use actors instead of classes, and see how it all falls apart:</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: #001080">actor</span><span style="color: #000000"> ActorA: </span><span style="color: #001080">GloballyIsolatedProtocol</span><span style="color: #000000"> { </span><span style="color: #008000">// ❌ Actor &#39;ActorA&#39; cannot conform to global actor isolated protocol &#39;GloballyIsolatedProtocol&#39;</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">doStuff</span><span style="color: #000000">() </span><span style="color: #008000">// ❌ Call to main actor-isolated global function &#39;doStuff()&#39; in a synchronous actor-isolated context</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: #001080">actor</span><span style="color: #000000"> ActorB: </span><span style="color: #001080">PerMemberIsolatedProtocol</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</span><span style="color: #000000">() { </span><span style="color: #008000">// ❌ Actor-isolated instance method &#39;method()&#39; cannot be used to satisfy main actor-isolated protocol requirement</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">doStuff</span><span style="color: #000000">() </span><span style="color: #008000">// ❌ Call to main actor-isolated global function &#39;doStuff()&#39; in a synchronous actor-isolated context</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>All these error messages are correct, even if they may be surprising at first.  You might even say:  so what?  That&#8217;s working as expected.</p>



<p>The problem is that there is <em>nothing</em> an actor can do to conform to <code>GloballyIsolatedProtocol</code>.  It is an actor-hostile protocol.  It is <em>unusable</em> by actors.</p>



<p>Whereas <code>PerMemberIsolatedProtocol</code> <em>can</em> be used by an actor:</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: #001080">actor</span><span style="color: #000000"> ActorB: </span><span style="color: #001080">PerMemberIsolatedProtocol</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    @</span><span style="color: #001080">MainActor</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">method</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">doStuff</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>Given that protocols are most often just used to express an interface requirement &#8211; e.g. for delegates, data sources, codability, etc &#8211; there is usually no reason why actors shouldn&#8217;t be allowed to conform to them.  Even if the protocol does have some isolation requirements, there&#8217;s no technical reason an actor can&#8217;t abide by those <em>if it&#8217;s given the chance</em>.</p>



<p>This leads into a more complicated aspect of isolation in Swift, regarding how actors aren&#8217;t necessarily restricted to their own isolation domain.  They can have methods and properties that are not isolated at all, or isolated to a <em>different</em> domain.  In Swift 6 they&#8217;ll even be able to have methods which are isolated <em>to different actors</em>!</p>



<p>So don&#8217;t make the mistake of assuming actors can only ever be off in their own isolated worlds.  And don&#8217;t needlessly exclude them from supporting your protocols.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/beware-of-specifying-isolation-requirements-for-whole-protocols/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7829</post-id>	</item>
		<item>
		<title>A brief introduction to type memory layout in Swift</title>
		<link>https://wadetregaskis.com/a-brief-introduction-to-type-memory-layout-in-swift/</link>
					<comments>https://wadetregaskis.com/a-brief-introduction-to-type-memory-layout-in-swift/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 28 Feb 2024 05:18:44 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[alignment]]></category>
		<category><![CDATA[memory efficiency]]></category>
		<category><![CDATA[memory layout]]></category>
		<category><![CDATA[padding]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7803</guid>

					<description><![CDATA[Most of the time we Swift programmers don&#8217;t really think about how our types &#8211; structs, classes, actors, enums, etc &#8211; are represented in memory. We just deal with them abstractly, knowing that somehow the compiler boils them down to a bunch of bytes, handling all the complexity of wrangling those bytes for us. However,&#8230; <a class="read-more-link" href="https://wadetregaskis.com/a-brief-introduction-to-type-memory-layout-in-swift/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Most of the time we Swift programmers don&#8217;t really think about how our types &#8211; structs, classes, actors, enums, etc &#8211; are represented in memory.  We just deal with them abstractly, knowing that somehow the compiler boils them down to a bunch of bytes, handling all the complexity of wrangling those bytes for us.</p>



<p>However, whether we realise it or not, we humans do actually determine how our types are laid out in memory, and how much memory they use &#8211; and in more subtle ways than you might realise.  We can have a significant impact on how <em>efficient</em> our memory usage is &#8211; and consequently runtime performance &#8211; based on things as innocuous as in what order we declare stored properties.</p>



<p>I&#8217;m going to start at the beginning, but if you find the foundations &amp; theory uninteresting, at least read the subsequent section headings for the key takeaways.</p>



<h1 class="wp-block-heading">Determining how Swift lays out a type</h1>



<p>The Swift standard library includes a handy utility, <code><a href="https://developer.apple.com/documentation/swift/memorylayout" data-wpel-link="external" target="_blank" rel="external noopener">MemoryLayout</a></code>.  It reports three attributes regarding how a target type is actually laid out in memory by the compiler:</p>



<ul class="wp-block-list">
<li>&#8220;<strong>Size</strong>&#8221; &#8211; the number of <em>contiguous</em> bytes required to store an instance of the type.  This is not necessarily the <em>optimal</em> size (as we&#8217;ll see later) nor the <em>actual</em> size, so it&#8217;s not terribly useful other than as a way to infer other, more interesting attributes.</li>



<li><strong>Stride</strong> &#8211; the <em>actual</em> number of bytes required to store an instance of the type<sup data-fn="13cd788d-5855-4a1e-a1bd-5d8bf2c204ed" class="fn"><a href="#13cd788d-5855-4a1e-a1bd-5d8bf2c204ed" id="13cd788d-5855-4a1e-a1bd-5d8bf2c204ed-link">1</a></sup>, taking into account its alignment requirements.</li>



<li><strong>Alignment</strong> &#8211; the address of any instance of the type must be evenly divisible by this power of two.<br><br>Alignment is a tricky concept and pivotal to how &amp; why data types are laid out the way they are (not just in Swift, but in practically all programming languages).  Thankfully, you don&#8217;t really need to understand alignment for the purposes of this post &#8211; just consider it a &#8220;magic&#8221; number dictated by hardware and compilers for performance and portability.  Or see e.g. <a href="https://en.wikipedia.org/wiki/Data_structure_alignment" data-wpel-link="external" target="_blank" rel="external noopener">Wikipedia</a> if you really want to dig into it.</li>
</ul>



<p>Using <code><a href="https://developer.apple.com/documentation/swift/mirror" data-wpel-link="external" target="_blank" rel="external noopener">Mirror</a></code>, it&#8217;s also possible to sum up the size of the stored properties for structs, classes, etc.  That tells you the <em>nominal</em> size, if alignment weren&#8217;t an issue.</p>



<p>The difference between the <em>nominal</em> <em>size</em> and <em>stride</em> (if any) is the <em>padding</em>.  Padding bytes aren&#8217;t actually used to store anything.  They are wasted space.  Minimising padding is generally good, especially if doing so doesn&#8217;t require breaking any alignment requirements.  Padding can be internal (between stored properties within the type) or trailing (after all stored properties within the type) &#8211; we&#8217;ll dig into the practical difference later.</p>



<p>So, armed will this knowledge and tools, let&#8217;s interrogate some types.</p>



<h1 class="wp-block-heading">Scalars tend to be pleasantly boring</h1>



<p>In general, scalars &#8211; individual numbers &amp; booleans &#8211; tend to have no padding and be aligned to their full size (known as <em>natural</em> alignment).  e.g.:</p>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Type</th><th class="has-text-align-center" data-align="center">Nominal Size</th><th class="has-text-align-center" data-align="center">Contiguous Size</th><th class="has-text-align-center" data-align="center">Alignment</th><th class="has-text-align-center" data-align="center">Actual Size (Stride)</th><th class="has-text-align-center" data-align="center">Internal Padding</th><th class="has-text-align-center" data-align="center">Trailing Padding</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right">Bool</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Int8 / UInt8</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Int16 / UInt16</td><td class="has-text-align-center" data-align="center">2</td><td class="has-text-align-center" data-align="center">2</td><td class="has-text-align-center" data-align="center">2</td><td class="has-text-align-center" data-align="center">2</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Int32 / UInt32</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Int64 / UInt64</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Float</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Double</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr></tbody></table></figure>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️  <code>Int</code> &amp; <code>UInt</code> vary in size across CPU ISAs, following the natural word size.  So, on 64-bit ISAs they are equivalent to <code>Int64</code> / <code>UInt64</code>, while on 32-bit ISAs they are equivalent to <code>Int32</code> / <code>UInt32</code>.</p>
</div></div>



<h1 class="wp-block-heading">Characters are expensive</h1>



<p><code><a href="https://developer.apple.com/documentation/swift/string" data-wpel-link="external" target="_blank" rel="external noopener">String</a></code> is a surprisingly and extremely complicated type, which is largely a topic for another time.  However, one important thing to note is its relationship to <code><a href="https://developer.apple.com/documentation/swift/character" data-wpel-link="external" target="_blank" rel="external noopener">Character</a></code>:</p>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Type</th><th class="has-text-align-center" data-align="center">Nominal Size</th><th class="has-text-align-center" data-align="center">Contiguous Size</th><th class="has-text-align-center" data-align="center">Alignment</th><th class="has-text-align-center" data-align="center">Actual Size (Stride)</th><th class="has-text-align-center" data-align="center">Internal Padding</th><th class="has-text-align-center" data-align="center">Trailing Padding</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right">Character</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">String</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr></tbody></table></figure>



<p>They&#8217;re the same!  <code>Character</code> is a lie &#8211; <a href="https://github.com/apple/swift/blob/bd46860dfbaa1471c6210595f4291873a49676f3/stdlib/public/core/Character.swift#L67" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s actually a <code>String</code></a>!</p>



<p>There is of course more to <code>Character</code> &#8211; although it <em>is</em> a string under the covers, its API enforces that it can only ever contain a <em>single</em> character.  But that&#8217;s about it.</p>



<p>The reason is that a single character has no upper bound on its byte size &#8211; these are Unicode characters (formally known as <em><a href="https://www.unicode.org/reports/tr29/" data-wpel-link="external" target="_blank" rel="external noopener">extended grapheme clusters</a></em>), not ASCII characters that fit trivially into a single byte.  Unicode characters can be composed of <em>multiple</em> Unicode scalars (a naive notion of a Unicode &#8216;character&#8217;, that is Unicode&#8217;s basic building block) &#8211; and that&#8217;s just scratching the surface.  <a href="https://andybargh.com/about/" data-wpel-link="external" target="_blank" rel="external noopener">Andy Bargh</a> has written <a href="https://andybargh.com/unicode/" data-wpel-link="external" target="_blank" rel="external noopener">a nice Swift-centric introduction to the fun of Unicode</a>, if you want to pull on that thread.</p>



<p>So, rather than reimplementing a <em>tremendous</em> amount of complex functionality, <code>Character</code> just uses <code>String</code> to do its dirty work.  It&#8217;s smart, but it means that <code>Character</code>s are as expensive as <code>String</code>s<sup data-fn="a966f782-5d20-411c-a04b-7b82986b6617" class="fn"><a href="#a966f782-5d20-411c-a04b-7b82986b6617" id="a966f782-5d20-411c-a04b-7b82986b6617-link">2</a></sup>.</p>



<p>Swift has no direct equivalent to the <code>char</code> type that many other languages have (i.e. an ASCII character; a byte).  The closest thing is <code>UInt8</code> (which is why you&#8217;ll often see <code>[UInt8]</code> or <code>UnsafeMutableBufferPointer&lt;UInt8></code> or similar when dealing with raw memory or C/C++ APIs).</p>



<p>The takeaway is:  don&#8217;t treat <code>Character</code> like <code>char</code>.  They are very different.  <code>Character</code> is much more expensive, in both memory and CPU usage.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ Note that the quoted size of 16 bytes is <em>not</em> necessarily the full size of a <code>Character</code> or <code>String</code>.  That includes a <em>small</em> subsection available for storing the actual string data, if that data happens to be very small.  If it&#8217;s not, then <code>String</code> makes a <em>separate</em> memory allocation for it.  That second allocation is always <em>at least</em> 16 bytes too.</p>



<p>The situation is even more complicated for <code>NSString</code>s bridged from Objective-C.</p>
</div></div>



<h1 class="wp-block-heading">How types compose into structs, classes, and actors</h1>



<p><em>Generally</em>, the rules are:</p>



<ul class="wp-block-list">
<li><em>Alignment</em> is the greatest of the components&#8217; alignments.</li>



<li><em>Stride</em> is the [contiguous] size padded up &#8211; if necessary &#8211; to satisfy the alignment.</li>



<li><em>Size</em> is complicated. 😝</li>
</ul>



<p>Let&#8217;s start with a simple example:</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">Composite</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"> a: </span><span style="color: #267F99">Int64</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> b: </span><span style="color: #267F99">Int32</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This <code>Composite</code> struct has a <em>nominal</em> size of twelve bytes &#8211; just the sum of its components.</p>



<p>Its components have alignment requirements of eight and four, respectively.  The greatest of those is eight, so that is the overall struct&#8217;s alignment.</p>



<p>Twelve is not a multiple of eight, so four bytes of padding are added to fix that, making the stride (the effective size) sixteen bytes.</p>



<p>But what happens if we change the order of the stored properties?</p>



<h2 class="wp-block-heading">Contiguous Size depends on stored property order</h2>



<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">CompositeB</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"> b: </span><span style="color: #267F99">Int32</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> a: </span><span style="color: #267F99">Int64</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>You might think this has no effect &#8211; after all, the struct is still storing the same things; what difference does the order make?</p>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Type</th><th class="has-text-align-center" data-align="center">Nominal Size</th><th class="has-text-align-center" data-align="center">Contiguous Size</th><th class="has-text-align-center" data-align="center">Alignment</th><th class="has-text-align-center" data-align="center">Actual Size (Stride)</th><th class="has-text-align-center" data-align="center">Internal Padding</th><th class="has-text-align-center" data-align="center">Trailing Padding</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right">Composite</td><td class="has-text-align-center" data-align="center">12</td><td class="has-text-align-center" data-align="center">12</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">4</td></tr><tr><td class="has-text-align-right" data-align="right">CompositeB</td><td class="has-text-align-center" data-align="center">12</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">0</td></tr></tbody></table></figure>



<p>For better or worse, Swift uses declaration order for the in-memory order of stored properties<sup data-fn="06c81bdc-8a1d-45ef-ba68-206050b9acd3" class="fn"><a href="#06c81bdc-8a1d-45ef-ba68-206050b9acd3" id="06c81bdc-8a1d-45ef-ba68-206050b9acd3-link">3</a></sup>.  And that can influence where padding goes.</p>



<p>In the case of our modified struct, <code>CompositeB</code>, the memory layout procedure is basically:</p>



<ol class="wp-block-list">
<li>Place the first item.  That&#8217;s an <code>Int32</code>, so it takes four bytes.  It requires four byte alignment, so the struct now requires [at least] four byte alignment.</li>



<li>Place the second item.  That&#8217;s an <code>Int64</code>.  It takes eight bytes.  It requires <em>eight</em> byte alignment.  So it cannot be placed immediately after the first item, as that would be an offset of and therefore alignment of four, which is not a multiple of eight.  So four bytes of padding are added, in-between the two items.<br><br>And the alignment requirement of the overall struct bumps up to eight, as well.</li>
</ol>



<p>The net result is that while the effective size is unchanged (at sixteen bytes), the Contiguous Size <em>has</em> changed &#8211; before it was technically just twelve bytes, but now it&#8217;s the full sixteen.  There&#8217;s still four bytes of padding in there that technically don&#8217;t matter and aren&#8217;t used, but because they&#8217;re now in the middle instead of at the end, they&#8217;re harder for the compiler to ignore.</p>



<h3 class="wp-block-heading">Trailing padding is better than internal padding</h3>



<p>In this specific example, this change doesn&#8217;t really matter.  The code generated for copying the struct will probably just use two load and two store instructions anyway, one for each stored property, so it doesn&#8217;t really matter where the padding is.  However, if the struct were bigger &#8211; more and/or larger stored properties &#8211; then copy might be implement as a call to <code>memcpy</code><sup data-fn="7bdf4f15-e127-4de0-9256-abf550dbb5b0" class="fn"><a href="#7bdf4f15-e127-4de0-9256-abf550dbb5b0" id="7bdf4f15-e127-4de0-9256-abf550dbb5b0-link">4</a></sup>.  That call won&#8217;t bother including any <em>trailing</em> padding because that&#8217;s easy to omit &#8211; just stop copying early &#8211; but it&#8217;ll have to copy the <em>internal</em> padding bytes even though their contents are irrelevant.  So it wastes time.</p>



<p>How <em>often</em> this manifests as a noticeable performance difference is much harder to say.  Probably you shouldn&#8217;t stress too much about this.  However, that doesn&#8217;t mean you can ignore stored property ordering, because…</p>



<h2 class="wp-block-heading">Actual Size (Stride) also depends on stored property order</h2>



<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">Composite2</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"> a: </span><span style="color: #267F99">Int64</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> b: </span><span style="color: #267F99">Int32</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> c: </span><span style="color: #267F99">Int32</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Composite2B</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"> b: </span><span style="color: #267F99">Int32</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> a: </span><span style="color: #267F99">Int64</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> c: </span><span style="color: #267F99">Int32</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Type</th><th class="has-text-align-center" data-align="center">Nominal Size</th><th class="has-text-align-center" data-align="center">Contiguous Size</th><th class="has-text-align-center" data-align="center">Alignment</th><th class="has-text-align-center" data-align="center">Actual Size (Stride)</th><th class="has-text-align-center" data-align="center">Internal Padding</th><th class="has-text-align-center" data-align="center">Trailing Padding</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right">Composite2</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Composite2B</td><td class="has-text-align-center" data-align="center">16</td><td class="has-text-align-center" data-align="center">20</td><td class="has-text-align-center" data-align="center">8</td><td class="has-text-align-center" data-align="center">24</td><td class="has-text-align-center" data-align="center">4</td><td class="has-text-align-center" data-align="center">4</td></tr></tbody></table></figure>



<p>Oh no!  Storing the exact same data in the &#8216;wrong&#8217; order has increased the actual memory usage by 50%!</p>



<p>It&#8217;s the same reason as for the simpler case covered earlier &#8211; every individual stored property must be stored with correct alignment, which means you can&#8217;t put an <code>Int64</code> immediately after a single <code>Int32</code>.  Having to put four bytes of padding in-between means your overall size (twenty bytes) isn&#8217;t a multiple of the overall alignment requirement (eight) so <em>another</em> four bytes of padding have to be added at the end.</p>



<p>Now, this doesn&#8217;t <em>always</em> matter.  If you only have a handful of instances of <code>Composite2B</code> in your program at any one time, the wasted memory will be insignificant.  But if you have many then it can add up to a significant cost.  So be on the lookout for this problem for any data types you use in large numbers.</p>



<p>Fortunately, changing the order of stored properties is always source-compatible, and is binary-compatible too (a.k.a. &#8220;ABI-compatible&#8221;) for <a href="https://github.com/apple/swift/blob/main/docs/LibraryEvolution.rst#classes" data-wpel-link="external" target="_blank" rel="external noopener">classes &amp; actors</a>, as well as <a href="https://github.com/apple/swift/blob/main/docs/LibraryEvolution.rst#structs" data-wpel-link="external" target="_blank" rel="external noopener">non-frozen structs</a>.  So even if you don&#8217;t catch the problem immediately, you might still be able to fix it.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ Although we&#8217;ve been looking at structs here, know that this all applies similarly to classes &amp; actors.  The main difference is that every class or actor &#8211; in fact, any reference type &#8211; is <em>at least</em> sixteen bytes irrespective of what stored properties it has.  Its alignment is likewise always <em>at least</em> sixteen bytes<sup data-fn="69e5ba4a-f757-497e-9fb7-1f1652436f33" class="fn"><a href="#69e5ba4a-f757-497e-9fb7-1f1652436f33" id="69e5ba4a-f757-497e-9fb7-1f1652436f33-link">5</a></sup>.  So the padding problems, and wasted space that result from them, tend to be even worse with classes &amp; actors.</p>
</div></div>



<h2 class="wp-block-heading">Bools are bytes, not bits</h2>



<p>In principle a boolean uses just a single bit of memory &#8211; true or false, 1 or 0.  Unfortunately, high-level languages like Swift tend to think of everything as <em>bytes</em>, not bits, and the smallest number of bytes is one &#8211; <em>eight</em> bits.</p>



<p>We saw this in the beginning, with <code>Bool</code> taking a whole byte even though it only uses an eighth of that memory.</p>



<p>Unfortunately, this carries over even to collections of <code>Bool</code>s.</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">ManyBooleans</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"> a: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> b: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> c: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> d: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> e: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> f: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> g: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> h: </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>In some languages, <code>ManyBooleans</code> would take up just one byte.  It has eight booleans, which need eight bits, which can be packed together into a single byte.  Perfect!</p>



<p>Unfortunately, Swift does not do that currently<sup data-fn="704b9877-db92-45a0-b3a3-07dec843e615" class="fn"><a href="#704b9877-db92-45a0-b3a3-07dec843e615" id="704b9877-db92-45a0-b3a3-07dec843e615-link">6</a></sup>.  In Swift, <code>ManyBooleans</code> is eight bytes.  It has an alignment of just one, so at least it never wastes any space with padding.  Nonetheless, it still takes up <em>eight times</em> as much memory as it needs to. 😢</p>



<p>This applies similarly to collections of <code>Bool</code>s, like <code>Array</code>s.  A <code>[Bool]</code> takes up one byte per entry, not one bit.  There is no native equivalent to C++&#8217;s <code><a href="https://en.cppreference.com/w/cpp/container/vector_bool" data-wpel-link="external" target="_blank" rel="external noopener">vector&lt;bool></a></code> in Swift (Swift does not support template specialisation in that sense), although <a href="https://github.com/apple/swift-collections" data-wpel-link="external" target="_blank" rel="external noopener">swift-collections</a> does contain <code><a href="https://swiftpackageindex.com/apple/swift-collections/main/documentation/bitcollections/bitarray" data-wpel-link="external" target="_blank" rel="external noopener">BitArray</a></code> (and <code><a href="https://swiftpackageindex.com/apple/swift-collections/main/documentation/bitcollections/bitset" data-wpel-link="external" target="_blank" rel="external noopener">BitSet</a></code>) collections that you can use manually (but you have to remember to use them!).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>🎉 I want to call out that these bit-efficient collections were added through a <a href="https://summerofcode.withgoogle.com" data-wpel-link="external" target="_blank" rel="external noopener">Google Summer of Code</a> project, by <a href="https://forums.swift.org/u/MahanazAtiqullah/summary" data-wpel-link="external" target="_blank" rel="external noopener">Mahanaz Atiqullah</a> with mentoring by <a href="https://forums.swift.org/u/david_smith/summary" data-wpel-link="external" target="_blank" rel="external noopener">David Smith</a>, <a href="https://forums.swift.org/u/lorentey/summary" data-wpel-link="external" target="_blank" rel="external noopener">Karoy Lorentey</a>, and <a href="https://forums.swift.org/u/kylemacomber/summary" data-wpel-link="external" target="_blank" rel="external noopener">Kyle Macomber</a>.  See <a href="https://forums.swift.org/t/bit-array-and-bit-set-api-review-the-end-of-a-gsoc-project/51396/1" data-wpel-link="external" target="_blank" rel="external noopener">her project presentation</a> for details and some benchmark numbers showing that these specialised collections are not just much more memory efficient but <em>much</em> faster as well.</p>
</div></div>



<h2 class="wp-block-heading">…except when they&#8217;re not</h2>



<figure class="wp-block-table aligncenter"><table><thead><tr><th class="has-text-align-right" data-align="right">Type</th><th class="has-text-align-center" data-align="center">Nominal Size</th><th class="has-text-align-center" data-align="center">Contiguous Size</th><th class="has-text-align-center" data-align="center">Alignment</th><th class="has-text-align-center" data-align="center">Actual Size (Stride)</th><th class="has-text-align-center" data-align="center">Internal Padding</th><th class="has-text-align-center" data-align="center">Trailing Padding</th></tr></thead><tbody><tr><td class="has-text-align-right" data-align="right">Bool</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr><tr><td class="has-text-align-right" data-align="right">Optional&lt;Bool></td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">0</td><td class="has-text-align-center" data-align="center">0</td></tr></tbody></table></figure>



<p>What witchcraft is this?!  We just saw that two <code>Bool</code>s do <em>not</em> share a byte in Swift, yet here we have conceptually two <code>Bool</code>s, and they&#8217;re doing exactly that!</p>



<p>This is because enums, basically.  They are a whole other kettle of fish.  I plan to do a follow-up post on their unique behaviour when it comes to memory layout.</p>


<ol class="wp-block-footnotes"><li id="13cd788d-5855-4a1e-a1bd-5d8bf2c204ed">For <em>most</em> purposes <em>in memory</em>.  It may be <em>possible</em> to pack the type into a smaller space (even without using formal compression) such as for serialisation into a file or to send over a network, but in general you can&#8217;t use the type in a Swift program if it&#8217;s not properly padded to its stride and correctly aligned. <a href="#13cd788d-5855-4a1e-a1bd-5d8bf2c204ed-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="a966f782-5d20-411c-a04b-7b82986b6617">Potentially even <em>more</em> expensive due to the indirection, although most <code>Character</code> APIs are inlinable and so tend to get compiled out by the optimiser. <a href="#a966f782-5d20-411c-a04b-7b82986b6617-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="06c81bdc-8a1d-45ef-ba68-206050b9acd3"><a href="https://forums.swift.org/t/optimal-stored-property-packing/70327" data-wpel-link="external" target="_blank" rel="external noopener">It generally doesn&#8217;t have to though</a>, which opens up the possibility that Swift will improve this behaviour in future. <a href="#06c81bdc-8a1d-45ef-ba68-206050b9acd3-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="7bdf4f15-e127-4de0-9256-abf550dbb5b0">The reasoning is complicated and subject to the whims of the compiler&#8217;s optimiser, but it factors in considerations such as the code size (of a single function call to <code>memcpy</code> vs potentially many separate load &amp; store instructions) and performance (at some point it becomes faster to just copy all the bytes together than copy every stored property&#8217;s bytes individually). <a href="#7bdf4f15-e127-4de0-9256-abf550dbb5b0-link" aria-label="Jump to footnote reference 4">↩︎</a></li><li id="69e5ba4a-f757-497e-9fb7-1f1652436f33">This is actually a consequence of how malloc is implemented on virtually all platforms &#8211; and certainly all of Apple&#8217;s &#8211; which is to return memory allocations that are at least sixteen bytes in size, and usually a power of two (so 16, 32, 64, 128, etc).  So your class might nominally only need 65 bytes of memory per instance, but it might end up using 128.  Which can make classes &amp; actors even <em>more</em> problematic regarding memory size and waste, than structs &amp; enums &#8211; a topic for a follow-up post, perhaps. <a href="#69e5ba4a-f757-497e-9fb7-1f1652436f33-link" aria-label="Jump to footnote reference 5">↩︎</a></li><li id="704b9877-db92-45a0-b3a3-07dec843e615"><a href="https://forums.swift.org/t/optimal-stored-property-packing/70327/5" data-wpel-link="external" target="_blank" rel="external noopener">It <em>could</em></a>, though, in future. <a href="#704b9877-db92-45a0-b3a3-07dec843e615-link" aria-label="Jump to footnote reference 6">↩︎</a></li></ol>


<h1 class="wp-block-heading">Appendices</h1>



<h2 class="wp-block-heading" id="helper-functions">Helper functions</h2>



<p>This post used the following core code for the data presented in the tables.  It&#8217;s pretty hacky &#8211; it doesn&#8217;t work correctly for all types, as the inline comments note, but it is sufficient for the relatively simple examples shown in this post.</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: #008000">// Table header</span></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Type&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #A31515">&quot;Nominal Size&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #A31515">&quot;Contiguous Size&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #A31515">&quot;Alignment&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #A31515">&quot;Actual Size (Stride)&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #A31515">&quot;Internal padding&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #A31515">&quot;Trailing Padding&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #001080">separator</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\t</span><span style="color: #A31515">&quot;</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Table row</span></span>
<span class="line"><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">sizeOf</span><span style="color: #000000">&lt;</span><span style="color: #267F99">T</span><span style="color: #000000">&gt;(</span><span style="color: #001080">_</span><span style="color: #000000"> </span><span style="color: #001080">value</span><span style="color: #000000">: </span><span style="color: #0070C1">T</span><span style="color: #000000">) -&gt; </span><span style="color: #001080">Int</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #0070C1">T</span><span style="color: #000000">&gt;.</span><span style="color: #001080">size</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">printLayout</span><span style="color: #000000">&lt;</span><span style="color: #267F99">T</span><span style="color: #000000">&gt;(</span><span style="color: #0000FF">of</span><span style="color: #000000"> </span><span style="color: #001080">type</span><span style="color: #000000">: </span><span style="color: #0070C1">T</span><span style="color: #000000">.</span><span style="color: #001080">Type</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #001080">example</span><span style="color: #000000">: </span><span style="color: #0070C1">T</span><span style="color: #000000">? = </span><span style="color: #001080">nil</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">    let </span><span style="color: #001080">nominalSize</span><span style="color: #000000"> = </span><span style="color: #001080">if</span><span style="color: #000000"> </span><span style="color: #001080">let</span><span style="color: #000000"> </span><span style="color: #001080">example</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Note: doesn&#39;t handle recursive types (nested structs, classes, actors, enums, etc).</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">//       Also not correct for primitive types (e.g. integers).</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">Mirror</span><span style="color: #000000">(</span><span style="color: #001080">reflecting</span><span style="color: #000000">: </span><span style="color: #267F99">example</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">            .children</span></span>
<span class="line"><span style="color: #000000">            .lazy</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #008000">// Have to force open the existential because Swift won&#39;t implicitly open Any in Swift 5 mode.</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #008000">// https://forums.swift.org/t/getting-the-size-of-any-value/62843/4</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #008000">// https://github.com/apple/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md#avoid-opening-when-the-existential-type-satisfies-requirements-in-swift-5</span></span>
<span class="line"><span style="color: #000000">            .map { </span><span style="color: #795E26">_openExistential</span><span style="color: #000000">(</span><span style="color: #001080">$0</span><span style="color: #000000">.</span><span style="color: #001080">value</span><span style="color: #000000">, </span><span style="color: #001080">do</span><span style="color: #000000">: </span><span style="color: #001080">sizeOf</span><span style="color: #000000">) }</span></span>
<span class="line"><span style="color: #000000">            .reduce(</span><span style="color: #001080">into:</span><span style="color: #000000"> </span><span style="color: #098658">0</span><span style="color: #000000">, +=)</span></span>
<span class="line"><span style="color: #000000">    } </span><span style="color: #001080">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        MemoryLayout&lt;T&gt;.</span><span style="color: #001080">size</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: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\(</span><span style="color: #A31515">type):&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">nominalSize</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #001080">T</span><span style="color: #000000">&gt;.size,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #001080">T</span><span style="color: #000000">&gt;.alignment,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #001080">T</span><span style="color: #000000">&gt;.stride,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #001080">T</span><span style="color: #000000">&gt;.size - </span><span style="color: #001080">nominalSize</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #001080">T</span><span style="color: #000000">&gt;.stride - </span><span style="color: #001080">MemoryLayout</span><span style="color: #000000">&lt;</span><span style="color: #001080">T</span><span style="color: #000000">&gt;.size,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #001080">separator</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\t</span><span style="color: #A31515">&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/a-brief-introduction-to-type-memory-layout-in-swift/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7803</post-id>	</item>
		<item>
		<title>Swift `print` takes multiple arguments</title>
		<link>https://wadetregaskis.com/swift-print-takes-multiple-arguments/</link>
					<comments>https://wadetregaskis.com/swift-print-takes-multiple-arguments/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 26 Feb 2024 17:00:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[print]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7787</guid>

					<description><![CDATA[I forget this too often, and write things like: Sometimes I feel like I&#8217;m really just writing stress-tests for the Swift syntax parser. The above can actually be written a bit more simply, as: Not just fewer characters, but conceptually simpler &#8211; fewer nested parenthesis, and no nested string literals at all. See, print&#8216;s function&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swift-print-takes-multiple-arguments/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I forget this too often, and write things 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: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Size: </span><span style="color: #EE0000">\(</span><span style="color: #A31515">thingy.getSize(&quot;</span><span style="color: #001080">Example</span><span style="color: #A31515">&quot;))&quot;</span><span style="color: #000000">)</span></span></code></pre></div>



<p>Sometimes I feel like I&#8217;m really just writing stress-tests for the Swift syntax parser.</p>



<p>The above can actually be written a bit more simply, as:</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">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Size:&quot;</span><span style="color: #000000">, </span><span style="color: #001080">thingy</span><span style="color: #000000">.</span><span style="color: #795E26">getSize</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Example&quot;</span><span style="color: #000000">))</span></span></code></pre></div>



<p>Not just fewer characters, but conceptually simpler &#8211; fewer nested parenthesis, and no nested string literals at all.</p>



<p>See, <code>print</code>&#8216;s function signature is not in fact merely <code>print(_ s: String)</code>, but:</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: #001080">func</span><span style="color: #000000"> </span><span style="color: #795E26">print</span><span style="color: #000000">&lt;</span><span style="color: #267F99">Target</span><span style="color: #000000">&gt;(</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">_</span><span style="color: #000000"> </span><span style="color: #001080">items</span><span style="color: #000000">: </span><span style="color: #001080">Any</span><span style="color: #000000">...,</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">separator</span><span style="color: #000000">: </span><span style="color: #001080">String</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: #001080">terminator</span><span style="color: #000000">: </span><span style="color: #001080">String</span><span style="color: #000000"> = </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>
<span class="line"><span style="color: #000000">    </span><span style="color: #001080">to</span><span style="color: #000000"> </span><span style="color: #001080">output</span><span style="color: #000000">: </span><span style="color: #001080">inout</span><span style="color: #000000"> </span><span style="color: #001080">Target</span></span>
<span class="line"><span style="color: #000000">) </span><span style="color: #001080">where</span><span style="color: #000000"> Target : </span><span style="color: #001080">TextOutputStream</span></span></code></pre></div>



<p>(I&#8217;m ignoring the specialisation that omits the <code>to</code> parameter &#8211; conceptually there&#8217;s just one <code>print</code> function)</p>



<p>It&#8217;s a pity that Swift still doesn&#8217;t have <a href="https://forums.swift.org/t/explicit-array-splat-for-variadic-functions/11326" data-wpel-link="external" target="_blank" rel="external noopener">splatting</a>, as that&#8217;d enable a nicer way of printing collections, than having to use <code>.joined(by: …)</code>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swift-print-takes-multiple-arguments/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7787</post-id>	</item>
		<item>
		<title>Hiding SwiftUI views</title>
		<link>https://wadetregaskis.com/hiding-swiftui-views/</link>
					<comments>https://wadetregaskis.com/hiding-swiftui-views/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 15 Feb 2024 01:42:38 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[AnyView]]></category>
		<category><![CDATA[EmptyView]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7711</guid>

					<description><![CDATA[There are several ways to hide a SwiftUI view, although they don&#8217;t all agree on what it means to hide the view. Do you want it to be invisible, or actually not there? To make it invisible you need only set its opacity to zero or use the hidden modifier. But the view will still&#8230; <a class="read-more-link" href="https://wadetregaskis.com/hiding-swiftui-views/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>There are several ways to hide a SwiftUI view, although they don&#8217;t all agree on what it <em>means</em> to hide the view.  Do you want it to be invisible, or actually not there?</p>



<p>To make it invisible you need only set its <a href="https://developer.apple.com/documentation/swiftui/view/opacity(_:)" data-wpel-link="external" target="_blank" rel="external noopener">opacity</a> to zero or use the <code><a href="https://developer.apple.com/documentation/swiftui/view/hidden()" data-wpel-link="external" target="_blank" rel="external noopener">hidden</a></code> modifier.  But the view will still be laid out just the same, and take up space in the GUI.</p>



<p>If you want to <em>actually</em> hide the view, so that it disappears completely, you can either not emit the view at all (e.g. conditionalise its existence with <code>if</code> or <code>switch</code>) or you can replace it with <code><a href="https://developer.apple.com/documentation/swiftui/emptyview/" data-wpel-link="external" target="_blank" rel="external noopener">EmptyView</a></code>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="654" height="545" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep.avif" alt="" class="wp-image-7715" srcset="https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep.avif 654w, https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep-256x213.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep@2x.avif 1308w, https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep-256x213@2x.avif 512w" sizes="auto, (max-width: 654px) 100vw, 654px" /></figure>
</div>


<p><code>EmptyView</code> is pretty marvellous in this respect.  Notice how even though it&#8217;s a real view &#8211; an actual value that you emit from a view builder &#8211; layout collections (e.g. <code>HStack</code>) don&#8217;t &#8220;see&#8221; it &#8211; there&#8217;s no visible gap, from doubling up on the cell padding, like there would be if they simply treated <code>EmptyView</code>s like 0 wide ⨉ 0 high views.</p>



<p>You might ask, when would you actually <em>need</em> EmptyView?  Wouldn&#8217;t you&#8217;d just use conditional code to hide a view, 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">struct</span><span style="color: #000000"> </span><span style="color: #267F99">MyView</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"> model: Model?</span></span>
<span class="line"><span style="color: #000000">    </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: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> model {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">Text</span><span style="color: #000000">(model.</span><span style="color: #001080">text</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>
<span class="line"><span style="color: #008000">// Now you can just use MyView(model: someOptional),</span></span>
<span class="line"><span style="color: #008000">// without having to unwrap it before every use.</span></span></code></pre></div>



<p>The above is making use of the &#8220;ViewBuilder mode&#8221;, to implicitly yield zero or more views which are automagically aggregated into a <code><a href="https://developer.apple.com/documentation/swiftui/tupleview" data-wpel-link="external" target="_blank" rel="external noopener">TupleView</a></code> (if you emit more than one), an optional view (if you dynamically emit zero or one), etc.  That&#8217;s very convenient, in simple cases like that.</p>



<p>But, &#8220;ViewBuilder mode&#8221; comes with a <em>lot</em> of limitations, including:</p>



<ul class="wp-block-list">
<li>Compiler diagnostics are far inferior to regular Swift code.</li>



<li>You cannot return early (i.e. use <code>return</code> statements) except by throwing an exception, which is not always semantically appropriate.</li>



<li>You cannot have nested functions.</li>



<li>You sometimes cannot use full Swift control flow syntax (e.g. switch statements).</li>
</ul>



<p>Fortunately, you can opt out of &#8220;ViewBuilder mode&#8221; by simply using an explicit <code>return</code> statement, but then you have to follow the usual rules for opaque return types, i.e. that the value be of the same type for every <code>return</code> statement.  You can use <code>AnyView</code> in concert with <code>EmptyView</code> to straighten the compiler&#8217;s knickers regarding the return type<sup data-fn="d83a6861-4b9d-4634-a63e-e5247eff8def" class="fn"><a href="#d83a6861-4b9d-4634-a63e-e5247eff8def" id="d83a6861-4b9d-4634-a63e-e5247eff8def-link">1</a></sup>, 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">struct</span><span style="color: #000000"> </span><span style="color: #267F99">MyView</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"> model: Model?</span></span>
<span class="line"><span style="color: #000000">    </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: #AF00DB">guard</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> model </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: #795E26">AnyView</span><span style="color: #000000">(</span><span style="color: #795E26">EmptyView</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: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">AnyView</span><span style="color: #000000">(&lt;actual view contents&gt;)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…or &#8211; if you don&#8217;t mind the return type being <code>Optional</code>, which SwiftUI itself doesn&#8217;t &#8211; you can use a partially opaque return type:</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">MyView</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"> model: Model?</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: </span><span style="color: #267F99">Optional</span><span style="color: #000000">&lt;some View&gt; {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> model </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"> Body.</span><span style="color: #001080">none</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: #AF00DB">return</span><span style="color: #000000"> &lt;actual view contents&gt;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>Thanks to <a href="https://forums.swift.org/u/dmt/summary" data-wpel-link="external" target="_blank" rel="external noopener">Dima Galimzianov</a> for <a href="https://forums.swift.org/t/cannot-use-nil-or-none-with-optional-some-t/70098/2" data-wpel-link="external" target="_blank" rel="external noopener">helping me figure out how to do that</a>.</p>



<p>Note that in the trivial cases shown above there&#8217;s no <em>particularly</em> compelling reason to use these forms instead of the &#8220;ViewBuilder mode&#8221;, but you could hopefully imagine how, as the conditional logic gets more complicated, it becomes increasingly impractical to avoid using guard, or early returns otherwise.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Note that while <code>AnyView</code> has a lot of FUD<sup data-fn="41306d14-0450-4ed5-a469-b2a06045dc54" class="fn"><a href="#41306d14-0450-4ed5-a469-b2a06045dc54" id="41306d14-0450-4ed5-a469-b2a06045dc54-link">2</a></sup> associated with it, as far as I can tell there&#8217;s really nothing wrong with it.  Most often it&#8217;s claimed to cause performance problems, but I&#8217;ve never detected that in my use, nor seen anyone provide a real example case of such.  And multiple people have gone looking for performance problems with <code>AnyView</code> and found nothing.  e.g. <a href="https://www.linkedin.com/in/nalexn/" data-wpel-link="external" target="_blank" rel="external noopener">Alexey Naumov</a>&#8216;s <a href="https://nalexn.github.io/anyview-vs-group/" data-wpel-link="external" target="_blank" rel="external noopener">Performance Battle: AnyView vs Group</a>.</p>
</div></div>



<p>In case you&#8217;re wondering, there&#8217;s no difference in show/hide behaviour either &#8211; it just works!</p>



<figure class="wp-block-video aligncenter fix-wolf-video-size"><video height="1086" style="aspect-ratio: 1304 / 1086;" width="1304" autoplay loop preload="auto" src="https://wadetregaskis.com/wp-content/uploads/2024/02/The-Big-Wolfie-Reveal.mp4" playsinline></video></figure>



<p>It&#8217;s possible that there&#8217;s some situations in which using <code>EmptyView</code> might cause animation issues, by confusing SwiftUI as to what the relationship is between view hierarchies over time, but I haven&#8217;t found that to be the case in practice.  And if it ever does crop up, you can always just explicitly <a href="https://developer.apple.com/documentation/swiftui/view/id(_:)" data-wpel-link="external" target="_blank" rel="external noopener">id</a> your views.</p>



<p><code>EmptyView</code> is one of those little sleeper features that seems irrelevant until you stumble upon a need for it, and then it&#8217;s <em>really</em> nice to have.</p>


<ol class="wp-block-footnotes"><li id="d83a6861-4b9d-4634-a63e-e5247eff8def">There&#8217;s technically another option: use a concrete return type instead.  But, that&#8217;s usually impractical &#8211; SwiftUI uses opaque return types <em>a lot</em>, such as for virtually all view modifiers, which force you to use opaque return types in turn.  And even when <em>that</em> isn&#8217;t an issue, good luck deducing the correct fully-qualified type name even for something as simple as a <code>LabeledContent</code>. <a href="#d83a6861-4b9d-4634-a63e-e5247eff8def-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="41306d14-0450-4ed5-a469-b2a06045dc54">Literally, &#8220;Fear, Uncertainty, and Doubt&#8221;.  Used here in that face-value sense, not to suggest any malice on anyone&#8217;s part. <a href="#41306d14-0450-4ed5-a469-b2a06045dc54-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/hiding-swiftui-views/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		<enclosure url="https://wadetregaskis.com/wp-content/uploads/2024/02/The-Big-Wolfie-Reveal.mp4" length="186396" type="video/mp4" />

			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/02/Wolf-amongst-the-sheep.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7711</post-id>	</item>
		<item>
		<title>Module verification must be enabled in order for Swift to use the module</title>
		<link>https://wadetregaskis.com/module-verification-must-be-enabled-in-order-for-swift-to-use-the-module/</link>
					<comments>https://wadetregaskis.com/module-verification-must-be-enabled-in-order-for-swift-to-use-the-module/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 05 Feb 2024 00:48:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[modulemap]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7652</guid>

					<description><![CDATA[Ugh. This was annoying to figure out. If you have a framework target in Xcode with a modulemap &#8211; e.g. because you&#8217;re wrapping a C or C++ library for use in Swift &#8211; you must keep the module verifier enabled (the ENABLE_MODULE_VERIFIER build setting) for that framework, otherwise any Swift targets using that framework won&#8217;t&#8230; <a class="read-more-link" href="https://wadetregaskis.com/module-verification-must-be-enabled-in-order-for-swift-to-use-the-module/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Ugh.  This was annoying to figure out.</p>



<p>If you have a framework target in Xcode with a <a href="https://www.swift.org/documentation/cxx-interop/#creating-a-clang-module" data-wpel-link="external" target="_blank" rel="external noopener">modulemap</a> &#8211; e.g. because you&#8217;re wrapping a C or C++ library for use in Swift &#8211; you <em>must</em> keep the module verifier enabled (the <code><a href="https://developer.apple.com/documentation/xcode/build-settings-reference#Enable-Module-Verifier" data-wpel-link="external" target="_blank" rel="external noopener">ENABLE_MODULE_VERIFIER</a></code> build setting) for that framework, otherwise any Swift targets using that framework won&#8217;t see the module (attempts to <code>import</code> the module will fail with the compiler claiming the module doesn&#8217;t exist).</p>



<p>Clearly the &#8220;verifier&#8221; does more than just verify the module.  Or, perhaps Xcode is being obnoxious and silently ignoring the module if it doesn&#8217;t detect an explicit pass from the verifier.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/module-verification-must-be-enabled-in-order-for-swift-to-use-the-module/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7652</post-id>	</item>
		<item>
		<title>SwiftUI drag &#038; drop does not support file promises</title>
		<link>https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/</link>
					<comments>https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 04 Feb 2024 19:04:31 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Drag & drop]]></category>
		<category><![CDATA[NSFilePromiseProvider]]></category>
		<category><![CDATA[NSItemProvider]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Transferable]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7646</guid>

					<description><![CDATA[SwiftUI doesn’t offer anything equivalent to NSFilePromiseProvider, i.e. to write data to the drop destination. You have to ditch SwiftUI and use AppKit&#8217;s drag &#38; drop APIs instead. FB13583826. Is that it? I know that&#8217;s not a very helpful in some sense, but I wasted days trying to figure out how to implement this very&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>SwiftUI doesn’t offer anything equivalent to <code><a href="https://developer.apple.com/documentation/appkit/nsfilepromiseprovider" data-wpel-link="external" target="_blank" rel="external noopener">NSFilePromiseProvider</a></code>, i.e. to write data to the drop destination.  You have to ditch SwiftUI and use AppKit&#8217;s drag &amp; drop APIs instead.</p>



<p>FB13583826.</p>



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



<p>I know that&#8217;s not a very helpful in some sense, but I wasted <em>days</em> trying to figure out how to implement this very basic drag and drop functionality, in SwiftUI.  From what I <a href="https://stackoverflow.com/questions/76327255/swiftui-receiving-nsfilepromisereceiver-via-nsitemprovider" data-wpel-link="external" target="_blank" rel="external noopener">found</a> <a href="https://stackoverflow.com/questions/69774792/use-nsitemprovider-in-combination-with-nsfilepromiseprovider" data-wpel-link="external" target="_blank" rel="external noopener">online</a>, I&#8217;m not the only person who&#8217;s struggled with this.  So hopefully this post saves others from long and frustrating searches.</p>



<p>A big part of the difficulty in answering this simple question &#8211; does SwiftUI support this or not &#8211; is that a lot of documentation (first &amp; third party) around SwiftUI&#8217;s APIs uses the word &#8220;promise&#8221; but not in the same way.  They merely mean that the pasteboard data is populated on first access, not that it actually follows the file promise protocol.</p>



<h2 class="wp-block-heading">Best alternative (other than ditching SwiftUI)</h2>



<p>The closest you can get is to write data into some arbitrary location that you have to choose blindly (not having any idea where the actual destination is), and then rely on the receiving app to move or copy the file from there.  It doesn&#8217;t matter, in this regard, whether you use <a href="https://developer.apple.com/documentation/swiftui/drag-and-drop#moving-transferable-items" data-wpel-link="external" target="_blank" rel="external noopener">the <code>Transferable</code>-based APIs</a> or <a href="https://developer.apple.com/documentation/swiftui/drag-and-drop#moving-items-using-item-providers" data-wpel-link="external" target="_blank" rel="external noopener">the <code>NSItemProvider</code>-based APIs</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: #000000">view.</span><span style="color: #001080">onDrag</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"> provider = </span><span style="color: #795E26">NSItemProvider</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    provider.</span><span style="color: #795E26">registerDataRepresentation</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: UTType.</span><span style="color: #001080">fileURL</span><span style="color: #000000">) {</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">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> tmpDir = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> FileManager.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #001080">url</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: .</span><span style="color: #001080">itemReplacementDirectory</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     </span><span style="color: #795E26">in</span><span style="color: #000000">: .</span><span style="color: #001080">userDomainMask</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     </span><span style="color: #795E26">appropriateFor</span><span style="color: #000000">: URL.</span><span style="color: #001080">temporaryDirectory</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                     </span><span style="color: #795E26">create</span><span style="color: #000000">: </span><span style="color: #0000FF">true</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #008000">// You&#39;ll need to provide the `suggestedFileName` based on the particulars of your use-case.</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> file = tmpDir.</span><span style="color: #795E26">appending</span><span style="color: #000000">(</span><span style="color: #795E26">component</span><span style="color: #000000">: suggestedFileName)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">try</span><span style="color: #000000"> bytes.</span><span style="color: #795E26">write</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000">: file, </span><span style="color: #795E26">options</span><span style="color: #000000">: .</span><span style="color: #001080">withoutOverwriting</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">$0</span><span style="color: #000000">(file.</span><span style="color: #001080">dataRepresentation</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 style="color: #AF00DB">catch</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">$0</span><span style="color: #000000">(</span><span style="color: #0000FF">nil</span><span style="color: #000000">, error)</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">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: #AF00DB">return</span><span style="color: #000000"> provider</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This makes it impossible to avoid unnecessary file copies, and requires you to keep the file around permanently, since you have no idea when the receiver is done with it.</p>



<p>e.g. if the Finder is the drop destination, you have no idea what volume the promised file was dropped on, so you don’t know which volume to create it on.  If you guess wrong, the Finder is forced to copy the file.  Not only does this confuse the user (by showing a copy badge on the mouse pointer) but it wastes time (in performing the copy) and the original file is left in place, requiring manual clean-up (which is impossible to do correctly because you have no idea when the receiving application is done with the temporary file &#8211; and the receiver might hold a reference to it permanently).</p>



<h2 class="wp-block-heading">Understanding the SwiftUI drag &amp; drop APIs</h2>



<p>Tangentially, I found most documentation on SwiftUI&#8217;s drag &amp; drop APIs &#8211; <em>especially</em> Apple&#8217;s &#8211; to be very poorly written, which is particularly frustrating in this case because the APIs are not intuitive in the slightest.</p>



<p>And most code / usage examples focus exclusively on trivial, disinteresting cases (e.g. dragging images around within a single window).</p>



<p>Only after I&#8217;d spend days reverse-engineering the APIs to figure out how they <em>actually</em> work and how they&#8217;re supposed to be used, did I stumble upon <a href="https://www.humancode.us/about.html" data-wpel-link="external" target="_blank" rel="external noopener">Dave Rahardja</a>&#8216;s <a href="https://www.humancode.us/2023/07/08/all-about-nsitemprovider.html" data-wpel-link="external" target="_blank" rel="external noopener">All about Item Providers</a>.  It&#8217;s <em>by far</em> the best documentation I&#8217;ve found on drag &amp; drop in SwiftUI.  Note though that Dave also uses the word &#8220;promise&#8221; a lot even though he&#8217;s never actually talking about actual file promises.</p>



<p>Also, I found that the newer and ostensibly better <code><a href="https://developer.apple.com/documentation/coretransferable/transferable" data-wpel-link="external" target="_blank" rel="external noopener">Transferable</a></code>-based API was harder to understand and harder to use than the <code><a href="https://developer.apple.com/documentation/foundation/nsitemprovider/" data-wpel-link="external" target="_blank" rel="external noopener">NSItemProvider</a></code>-based APIs.  I recommend going straight to, and sticking with, the latter.  Your code will be simpler and more reliable.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swiftui-drag-drop-does-not-support-file-promises/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7646</post-id>	</item>
		<item>
		<title>Bad API example: FileManager&#8217;s url(for:in:appropriateFor:create:)</title>
		<link>https://wadetregaskis.com/bad-api-example-filemanagers-urlforinappropriateforcreate/</link>
					<comments>https://wadetregaskis.com/bad-api-example-filemanagers-urlforinappropriateforcreate/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 31 Jan 2024 20:40:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[FileManager]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7631</guid>

					<description><![CDATA[I find FileManager&#8216;s url(for:in:appropriateFor:create:) to be very unintuitive. It seems to have multiple, largely-orthogonal functions. It can provide paths to common folders (albeit badly). It can create temporary folders. It can locate volume-specific bins (Trash folders). It is an example of bad API design. Specifically, regarding cohesion: the principle that an API should have one&#8230; <a class="read-more-link" href="https://wadetregaskis.com/bad-api-example-filemanagers-urlforinappropriateforcreate/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I find <code><a href="https://developer.apple.com/documentation/foundation/filemanager" data-wpel-link="external" target="_blank" rel="external noopener">FileManager</a></code>&#8216;s <code><a href="https://developer.apple.com/documentation/foundation/filemanager/1407693-url" data-wpel-link="external" target="_blank" rel="external noopener">url(for:in:appropriateFor:create:)</a></code> to be very unintuitive.  It seems to have multiple, largely-orthogonal functions.  It can provide paths to common folders (albeit badly).  It can create temporary folders.  It can locate volume-specific bins (Trash folders).</p>



<p>It is an example of bad API design.  Specifically, regarding cohesion: the principle that an API should have one purpose.  A litmus test for this is whether all the method parameters are always applicable<sup data-fn="dd2453d4-5a23-4b69-983a-8d13728f39f4" class="fn"><a href="#dd2453d4-5a23-4b69-983a-8d13728f39f4" id="dd2453d4-5a23-4b69-983a-8d13728f39f4-link">1</a></sup>.</p>



<p>It wasn&#8217;t until I wrote a test driver which explores its entire parameter space, that I was finally able to grok what the hell it&#8217;s doing and delineate its multiple modes of operation.</p>



<p>I&#8217;ve contrasted it with the results from its sibling <code><a href="https://developer.apple.com/documentation/foundation/filemanager/1407726-urls" data-wpel-link="external" target="_blank" rel="external noopener">urls(for:in:)</a></code>, to better understand what it&#8217;s doing (and expose some more of its flaws).</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Test driver</summary>
<p>You can run this in a Swift playground, or as CLI app, but to observe the behaviour inside an <a href="https://developer.apple.com/documentation/security/app_sandbox" data-wpel-link="external" target="_blank" rel="external noopener">App Sandbox</a> it&#8217;s easiest to create a new GUI app in Xcode and just dump this into the @main <code>App</code> struct&#8217;s <code>init</code> method.</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"> fm = FileManager.</span><span style="color: #001080">default</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> searchPathDirectories = [(</span><span style="color: #A31515">&quot;applicationDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">applicationDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;demoApplicationDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">demoApplicationDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;developerApplicationDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">developerApplicationDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;adminApplicationDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">adminApplicationDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;libraryDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">libraryDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;developerDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">developerDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;userDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">userDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;documentationDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">documentationDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;documentDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">documentDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;coreServiceDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">coreServiceDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;autosavedInformationDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">autosavedInformationDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;desktopDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">desktopDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;cachesDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">cachesDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;applicationSupportDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">applicationSupportDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;downloadsDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">downloadsDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;inputMethodsDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">inputMethodsDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;moviesDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">moviesDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;musicDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">musicDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;picturesDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">picturesDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;printerDescriptionDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">printerDescriptionDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;sharedPublicDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">sharedPublicDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;preferencePanesDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">preferencePanesDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;applicationScriptsDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">applicationScriptsDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;itemReplacementDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">itemReplacementDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;allApplicationsDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">allApplicationsDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;allLibrariesDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">allLibrariesDirectory</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;trashDirectory&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDirectory</span><span style="color: #000000">.</span><span style="color: #001080">trashDirectory</span><span style="color: #000000">)]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> searchPathDomainMasks = [(</span><span style="color: #A31515">&quot;userDomainMask&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDomainMask</span><span style="color: #000000">.</span><span style="color: #001080">userDomainMask</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;localDomainMask&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDomainMask</span><span style="color: #000000">.</span><span style="color: #001080">localDomainMask</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;systemDomainMask&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDomainMask</span><span style="color: #000000">.</span><span style="color: #001080">systemDomainMask</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             (</span><span style="color: #A31515">&quot;networkDomainMask&quot;</span><span style="color: #000000">, FileManager.</span><span style="color: #001080">SearchPathDomainMask</span><span style="color: #000000">.</span><span style="color: #001080">networkDomainMask</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                             </span><span style="color: #008000">/*(&quot;allDomainsMask&quot;, FileManager.SearchPathDomainMask.allDomainsMask)*/</span><span style="color: #000000">]</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;urls(for:in:):&quot;</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">for</span><span style="color: #000000"> (dirName, dir) </span><span style="color: #AF00DB">in</span><span style="color: #000000"> searchPathDirectories {</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;</span><span style="color: #EE0000">\n</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">dirName</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">    </span><span style="color: #AF00DB">for</span><span style="color: #000000"> (domainName, domain) </span><span style="color: #AF00DB">in</span><span style="color: #000000"> searchPathDomainMasks {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> dirs = fm.</span><span style="color: #795E26">urls</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: dir, </span><span style="color: #795E26">in</span><span style="color: #000000">: domain)</span></span>
<span class="line"><span style="color: #000000">            .</span><span style="color: #795E26">map</span><span style="color: #000000"> { </span><span style="color: #0000FF">$0</span><span style="color: #000000">.</span><span style="color: #795E26">path</span><span style="color: #000000">(</span><span style="color: #795E26">percentEncoded</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">joined</span><span style="color: #000000">(</span><span style="color: #795E26">separator</span><span style="color: #000000">: </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: #267F99">String</span><span style="color: #000000">(</span><span style="color: #795E26">repeating</span><span style="color: #000000">: </span><span style="color: #A31515">&quot; &quot;</span><span style="color: #000000">, </span><span style="color: #795E26">count</span><span style="color: #000000">: </span><span style="color: #098658">23</span><span style="color: #000000">))</span></span>
<span class="line"></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;    </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">domainName</span><span style="color: #0000FF">)</span><span style="color: #A31515">: </span><span style="color: #0000FF">\(</span><span style="color: #267F99">String</span><span style="color: #000000FF">(</span><span style="color: #795E26">repeating</span><span style="color: #000000FF">: </span><span style="color: #A31515">&quot; &quot;</span><span style="color: #000000FF">, </span><span style="color: #795E26">count</span><span style="color: #000000FF">: </span><span style="color: #098658">17</span><span style="color: #000000FF"> </span><span style="color: #000000">-</span><span style="color: #000000FF"> domainName.</span><span style="color: #001080">count</span><span style="color: #000000FF">)</span><span style="color: #0000FF">)\(</span><span style="color: #000000FF">dirs</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 style="color: #000000">}</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;</span><span style="color: #EE0000">\n\n</span><span style="color: #A31515">url(for:in:appropriateFor:create:):&quot;</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> paths = [</span><span style="color: #0000FF">nil</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">             </span><span style="color: #795E26">URL</span><span style="color: #000000">(</span><span style="color: #795E26">filePath</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">             FileManager.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #001080">temporaryDirectory</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">             </span><span style="color: #795E26">URL</span><span style="color: #000000">(</span><span style="color: #795E26">filePath</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;/Volumes/Flash/&quot;</span><span style="color: #000000">)]</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> pathDesc: (URL?) -&gt; </span><span style="color: #267F99">String</span><span style="color: #000000"> = { </span><span style="color: #0000FF">$0</span><span style="color: #000000">?.</span><span style="color: #795E26">path</span><span style="color: #000000">(</span><span style="color: #795E26">percentEncoded</span><span style="color: #000000">: </span><span style="color: #0000FF">false</span><span style="color: #000000">) ?? </span><span style="color: #A31515">&quot;nil&quot;</span><span style="color: #000000"> }</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> maxPathLength = paths.</span><span style="color: #795E26">map</span><span style="color: #000000"> { </span><span style="color: #795E26">pathDesc</span><span style="color: #000000">(</span><span style="color: #0000FF">$0</span><span style="color: #000000">).</span><span style="color: #001080">count</span><span style="color: #000000"> }.</span><span style="color: #795E26">max</span><span style="color: #000000">() ?? </span><span style="color: #098658">0</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">for</span><span style="color: #000000"> (dirName, dir) </span><span style="color: #AF00DB">in</span><span style="color: #000000"> searchPathDirectories {</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;</span><span style="color: #EE0000">\n</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">dirName</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">    </span><span style="color: #AF00DB">for</span><span style="color: #000000"> (domainName, domain) </span><span style="color: #AF00DB">in</span><span style="color: #000000"> searchPathDomainMasks {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">var</span><span style="color: #000000"> results = [</span><span style="color: #267F99">String</span><span style="color: #000000">: </span><span style="color: #267F99">String</span><span style="color: #000000">]()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">for</span><span style="color: #000000"> appropriateForPath </span><span style="color: #AF00DB">in</span><span style="color: #000000"> paths {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> path: </span><span style="color: #267F99">String</span></span>
<span class="line"></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">                </span><span style="color: #0000FF">let</span><span style="color: #000000"> folderURL = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> FileManager.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #001080">url</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: dir,</span></span>
<span class="line"><span style="color: #000000">                                                            </span><span style="color: #795E26">in</span><span style="color: #000000">: domain,</span></span>
<span class="line"><span style="color: #000000">                                                            </span><span style="color: #795E26">appropriateFor</span><span style="color: #000000">: appropriateForPath,</span></span>
<span class="line"><span style="color: #000000">                                                            </span><span style="color: #795E26">create</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">                path = </span><span style="color: #795E26">pathDesc</span><span style="color: #000000">(folderURL)</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">                path = </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>
<span class="line"><span style="color: #000000">            }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            results[</span><span style="color: #795E26">pathDesc</span><span style="color: #000000">(appropriateForPath)] = path</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">let</span><span style="color: #000000"> uniquePaths = </span><span style="color: #267F99">Set</span><span style="color: #000000">(results.</span><span style="color: #001080">values</span><span style="color: #000000">)</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: #098658">1</span><span style="color: #000000"> == uniquePaths.</span><span style="color: #001080">count</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;    </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">domainName</span><span style="color: #0000FF">)</span><span style="color: #A31515">: </span><span style="color: #0000FF">\(</span><span style="color: #267F99">String</span><span style="color: #000000FF">(</span><span style="color: #795E26">repeating</span><span style="color: #000000FF">: </span><span style="color: #A31515">&quot; &quot;</span><span style="color: #000000FF">, </span><span style="color: #795E26">count</span><span style="color: #000000FF">: </span><span style="color: #098658">17</span><span style="color: #000000FF"> </span><span style="color: #000000">-</span><span style="color: #000000FF"> domainName.</span><span style="color: #001080">count</span><span style="color: #000000FF">)</span><span style="color: #0000FF">)\(</span><span style="color: #000000FF">uniquePaths.</span><span style="color: #001080">first</span><span style="color: #000000">!</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: #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;    </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">domainName</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">            </span><span style="color: #AF00DB">for</span><span style="color: #000000"> (appropriateForPath, path) </span><span style="color: #AF00DB">in</span><span style="color: #000000"> results.</span><span style="color: #795E26">sorted</span><span style="color: #000000">(</span><span style="color: #795E26">by</span><span style="color: #000000">: { </span><span style="color: #0000FF">$0</span><span style="color: #000000">.</span><span style="color: #001080">key</span><span style="color: #000000"> &lt; </span><span style="color: #0000FF">$1</span><span style="color: #000000">.</span><span style="color: #001080">key</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;        </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">appropriateForPath</span><span style="color: #0000FF">)</span><span style="color: #A31515">: </span><span style="color: #0000FF">\(</span><span style="color: #267F99">String</span><span style="color: #000000FF">(</span><span style="color: #795E26">repeating</span><span style="color: #000000FF">: </span><span style="color: #A31515">&quot; &quot;</span><span style="color: #000000FF">, </span><span style="color: #795E26">count</span><span style="color: #000000FF">: maxPathLength </span><span style="color: #000000">-</span><span style="color: #000000FF"> appropriateForPath.</span><span style="color: #001080">count</span><span style="color: #000000FF">)</span><span style="color: #0000FF">)\(</span><span style="color: #000000FF">path</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 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>
</details>



<p>And here&#8217;s the output:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open><summary>When running inside an <a href="https://developer.apple.com/documentation/security/app_sandbox" data-wpel-link="external" target="_blank" rel="external noopener">App Sandbox</a>:</summary>
<pre class="wp-block-preformatted">urls(for:in:):

applicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
    localDomainMask:   /Applications/
    systemDomainMask:  /System/Applications/
                       /System/Cryptexes/App/System/Applications/
    networkDomainMask: /Network/Applications/

demoApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Demos/
    localDomainMask:   /Applications/Demos/
    systemDomainMask:  /Applications/Demos/
    networkDomainMask: /Network/Applications/Demos/

developerApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/Applications/
    localDomainMask:   /Developer/Applications/
    systemDomainMask:  /Developer/Applications/
    networkDomainMask: /Network/Developer/Applications/

adminApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Utilities/
    localDomainMask:   /Applications/Utilities/
    systemDomainMask:  /System/Applications/Utilities/
                       /System/Cryptexes/App/System/Applications/Utilities/
    networkDomainMask: /Network/Applications/Utilities/

libraryDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
    localDomainMask:   /Library/
    systemDomainMask:  /System/Library/
                       /System/Cryptexes/App/System/Library/
    networkDomainMask: /Network/Library/

developerDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/
    localDomainMask:   /Developer/
    systemDomainMask:  /Developer/
    networkDomainMask: /Network/Developer/

userDirectory:
    userDomainMask:    
    localDomainMask:   /Users/
    systemDomainMask:  
    networkDomainMask: /Network/Users/

documentationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Documentation/
    localDomainMask:   /Library/Documentation/
    systemDomainMask:  /System/Library/Documentation/
                       /System/Cryptexes/App/System/Library/Documentation/
    networkDomainMask: /Network/Library/Documentation/

documentDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Documents/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

coreServiceDirectory:
    userDomainMask:    
    localDomainMask:   
    systemDomainMask:  /System/Library/CoreServices/
                       /System/Cryptexes/App/System/Library/CoreServices/
    networkDomainMask: 

autosavedInformationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Autosave Information/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

desktopDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Desktop/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

cachesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Caches/
    localDomainMask:   /Library/Caches/
    systemDomainMask:  /System/Library/Caches/
    networkDomainMask: 

applicationSupportDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Application Support/
    localDomainMask:   /Library/Application Support/
    systemDomainMask:  /Library/Application Support/
    networkDomainMask: /Network/Library/Application Support/

downloadsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Downloads/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

inputMethodsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Input Methods/
    localDomainMask:   /Library/Input Methods/
    systemDomainMask:  /System/Library/Input Methods/
                       /System/Cryptexes/App/System/Library/Input Methods/
    networkDomainMask: /Network/Library/Input Methods/

moviesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Movies/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

musicDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Music/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

picturesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Pictures/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

printerDescriptionDirectory:
    userDomainMask:    
    localDomainMask:   
    systemDomainMask:  /System/Library/Printers/PPDs/
                       /System/Cryptexes/App/System/Library/Printers/PPDs/
    networkDomainMask: 

sharedPublicDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Public/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

preferencePanesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/PreferencePanes/
    localDomainMask:   /Library/PreferencePanes/
    systemDomainMask:  /System/Library/PreferencePanes/
                       /System/Cryptexes/App/System/Library/PreferencePanes/
    networkDomainMask: 

applicationScriptsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

itemReplacementDirectory:
    userDomainMask:    
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

allApplicationsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
                       /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Utilities/
                       /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/Applications/
                       /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Demos/
    localDomainMask:   /Applications/
                       /Applications/Utilities/
                       /Developer/Applications/
                       /Applications/Demos/
    systemDomainMask:  /System/Applications/
                       /System/Applications/Utilities/
                       /System/Developer/Applications/
                       /System/Applications/Demos/
                       /System/Cryptexes/App/System/Applications/
                       /System/Cryptexes/App/System/Applications/Utilities/
                       /System/Cryptexes/App/System/Developer/Applications/
                       /System/Cryptexes/App/System/Applications/Demos/
    networkDomainMask: /Network/Applications/
                       /Network/Applications/Utilities/
                       /Network/Developer/Applications/
                       /Network/Applications/Demos/

allLibrariesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
                       /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/
    localDomainMask:   /Library/
                       /Developer/
    systemDomainMask:  /System/Library/
                       /Developer/
                       /System/Cryptexes/App/System/Library/
                       /System/Cryptexes/App/System/Developer/
    networkDomainMask: /Network/Library/
                       /Network/Developer/

trashDirectory:
    userDomainMask:    /Users/SadPanda/.Trash/
    localDomainMask:   /Users/SadPanda/.Trash/
    systemDomainMask:  
    networkDomainMask: 


url(for:in:appropriateFor:create:):

applicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
    localDomainMask:   /Applications/
    systemDomainMask:  /System/Applications/
    networkDomainMask: /Network/Applications/

demoApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Demos/
    localDomainMask:   /Applications/Demos/
    systemDomainMask:  /Applications/Demos/
    networkDomainMask: /Network/Applications/Demos/

developerApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/Applications/
    localDomainMask:   /Developer/Applications/
    systemDomainMask:  /Developer/Applications/
    networkDomainMask: /Network/Developer/Applications/

adminApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Utilities/
    localDomainMask:   /Applications/Utilities/
    systemDomainMask:  /System/Applications/Utilities/
    networkDomainMask: /Network/Applications/Utilities/

libraryDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
    localDomainMask:   /Library/
    systemDomainMask:  /System/Library/
    networkDomainMask: /Network/Library/

developerDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/
    localDomainMask:   /Developer/
    systemDomainMask:  /Developer/
    networkDomainMask: /Network/Developer/

userDirectory:
    userDomainMask:    ERROR (nilError)
    localDomainMask:   /Users/
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: /Network/Users/

documentationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Documentation/
    localDomainMask:   /Library/Documentation/
    systemDomainMask:  /System/Library/Documentation/
    networkDomainMask: /Network/Library/Documentation/

documentDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Documents/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

coreServiceDirectory:
    userDomainMask:    ERROR (nilError)
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  /System/Library/CoreServices/
    networkDomainMask: ERROR (nilError)

autosavedInformationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Autosave Information/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

desktopDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Desktop/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

cachesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Caches/
    localDomainMask:   /Library/Caches/
    systemDomainMask:  /System/Library/Caches/
    networkDomainMask: ERROR (nilError)

applicationSupportDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Application Support/
    localDomainMask:   /Library/Application Support/
    systemDomainMask:  /Library/Application Support/
    networkDomainMask: /Network/Library/Application Support/

downloadsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Downloads/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

inputMethodsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Input Methods/
    localDomainMask:   /Library/Input Methods/
    systemDomainMask:  /System/Library/Input Methods/
    networkDomainMask: /Network/Library/Input Methods/

moviesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Movies/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

musicDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Music/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

picturesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Pictures/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

printerDescriptionDirectory:
    userDomainMask:    ERROR (nilError)
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  /System/Library/Printers/PPDs/
    networkDomainMask: ERROR (nilError)

sharedPublicDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Public/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

preferencePanesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/PreferencePanes/
    localDomainMask:   /Library/PreferencePanes/
    systemDomainMask:  /System/Library/PreferencePanes/
    networkDomainMask: ERROR (nilError)

applicationScriptsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

itemReplacementDirectory:
    userDomainMask:
        /:                                                               /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/TemporaryItems/NSIRD_MyApp_oflT6r/
        /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/TemporaryItems/NSIRD_MyApp_xkFfky/
        /Volumes/Other/:                                                 /Volumes/Other/.TemporaryItems/folders.501/TemporaryItems/NSIRD_MyApp_bLj7gn/
        nil:                                                             ERROR (nilError)
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

allApplicationsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
    localDomainMask:   /Applications/
    systemDomainMask:  /System/Applications/Demos/
    networkDomainMask: /Network/Applications/

allLibrariesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
    localDomainMask:   /Library/
    systemDomainMask:  /Developer/
    networkDomainMask: /Network/Library/

trashDirectory:
    userDomainMask:
        /:                                                               /Users/SadPanda/.Trash/
        /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
        /Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
        nil:                                                             /Users/SadPanda/.Trash/
    localDomainMask:
        /:                                                               /Users/SadPanda/.Trash/
        /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
        /Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
        nil:                                                             /Users/SadPanda/.Trash/
    systemDomainMask:
        /:                                                               /Users/SadPanda/.Trash/
        /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
        /Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
        nil:                                                             ERROR (nilError)
    networkDomainMask:
        /:                                                               /Users/SadPanda/.Trash/
        /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
        /Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
        nil:                                                             ERROR (nilError)</pre>
</details>



<p>The output outside of an App Sandbox is pretty similar, just different paths in some cases as you&#8217;d expect.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>When running without App Sandboxing</summary>
<pre class="wp-block-preformatted">urls(for:in:):

applicationDirectory:
    userDomainMask:    /Users/SadPanda/Applications/
    localDomainMask:   /Applications/
    systemDomainMask:  /System/Applications/
                       /System/Cryptexes/App/System/Applications/
    networkDomainMask: /Network/Applications/

demoApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Applications/Demos/
    localDomainMask:   /Applications/Demos/
    systemDomainMask:  /Applications/Demos/
    networkDomainMask: /Network/Applications/Demos/

developerApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Developer/Applications/
    localDomainMask:   /Developer/Applications/
    systemDomainMask:  /Developer/Applications/
    networkDomainMask: /Network/Developer/Applications/

adminApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Applications/Utilities/
    localDomainMask:   /Applications/Utilities/
    systemDomainMask:  /System/Applications/Utilities/
                       /System/Cryptexes/App/System/Applications/Utilities/
    networkDomainMask: /Network/Applications/Utilities/

libraryDirectory:
    userDomainMask:    /Users/SadPanda/Library/
    localDomainMask:   /Library/
    systemDomainMask:  /System/Library/
                       /System/Cryptexes/App/System/Library/
    networkDomainMask: /Network/Library/

developerDirectory:
    userDomainMask:    /Users/SadPanda/Developer/
    localDomainMask:   /Developer/
    systemDomainMask:  /Developer/
    networkDomainMask: /Network/Developer/

userDirectory:
    userDomainMask:    
    localDomainMask:   /Users/
    systemDomainMask:  
    networkDomainMask: /Network/Users/

documentationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Documentation/
    localDomainMask:   /Library/Documentation/
    systemDomainMask:  /System/Library/Documentation/
                       /System/Cryptexes/App/System/Library/Documentation/
    networkDomainMask: /Network/Library/Documentation/

documentDirectory:
    userDomainMask:    /Users/SadPanda/Documents/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

coreServiceDirectory:
    userDomainMask:    
    localDomainMask:   
    systemDomainMask:  /System/Library/CoreServices/
                       /System/Cryptexes/App/System/Library/CoreServices/
    networkDomainMask: 

autosavedInformationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Autosave Information/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

desktopDirectory:
    userDomainMask:    /Users/SadPanda/Desktop/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

cachesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Caches/
    localDomainMask:   /Library/Caches/
    systemDomainMask:  /System/Library/Caches/
    networkDomainMask: 

applicationSupportDirectory:
    userDomainMask:    /Users/SadPanda/Library/Application Support/
    localDomainMask:   /Library/Application Support/
    systemDomainMask:  /Library/Application Support/
    networkDomainMask: /Network/Library/Application Support/

downloadsDirectory:
    userDomainMask:    /Users/SadPanda/Downloads/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

inputMethodsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Input Methods/
    localDomainMask:   /Library/Input Methods/
    systemDomainMask:  /System/Library/Input Methods/
                       /System/Cryptexes/App/System/Library/Input Methods/
    networkDomainMask: /Network/Library/Input Methods/

moviesDirectory:
    userDomainMask:    /Users/SadPanda/Movies/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

musicDirectory:
    userDomainMask:    /Users/SadPanda/Music/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

picturesDirectory:
    userDomainMask:    /Users/SadPanda/Pictures/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

printerDescriptionDirectory:
    userDomainMask:    
    localDomainMask:   
    systemDomainMask:  /System/Library/Printers/PPDs/
                       /System/Cryptexes/App/System/Library/Printers/PPDs/
    networkDomainMask: 

sharedPublicDirectory:
    userDomainMask:    /Users/SadPanda/Public/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

preferencePanesDirectory:
    userDomainMask:    /Users/SadPanda/Library/PreferencePanes/
    localDomainMask:   /Library/PreferencePanes/
    systemDomainMask:  /System/Library/PreferencePanes/
                       /System/Cryptexes/App/System/Library/PreferencePanes/
    networkDomainMask: 

applicationScriptsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

itemReplacementDirectory:
    userDomainMask:    
    localDomainMask:   
    systemDomainMask:  
    networkDomainMask: 

allApplicationsDirectory:
    userDomainMask:    /Users/SadPanda/Applications/
                       /Users/SadPanda/Applications/Utilities/
                       /Users/SadPanda/Developer/Applications/
                       /Users/SadPanda/Applications/Demos/
    localDomainMask:   /Applications/
                       /Applications/Utilities/
                       /Developer/Applications/
                       /Applications/Demos/
    systemDomainMask:  /System/Applications/
                       /System/Applications/Utilities/
                       /System/Developer/Applications/
                       /System/Applications/Demos/
                       /System/Cryptexes/App/System/Applications/
                       /System/Cryptexes/App/System/Applications/Utilities/
                       /System/Cryptexes/App/System/Developer/Applications/
                       /System/Cryptexes/App/System/Applications/Demos/
    networkDomainMask: /Network/Applications/
                       /Network/Applications/Utilities/
                       /Network/Developer/Applications/
                       /Network/Applications/Demos/

allLibrariesDirectory:
    userDomainMask:    /Users/SadPanda/Library/
                       /Users/SadPanda/Developer/
    localDomainMask:   /Library/
                       /Developer/
    systemDomainMask:  /System/Library/
                       /Developer/
                       /System/Cryptexes/App/System/Library/
                       /System/Cryptexes/App/System/Developer/
    networkDomainMask: /Network/Library/
                       /Network/Developer/

trashDirectory:
    userDomainMask:    /Users/SadPanda/.Trash/
    localDomainMask:   /Users/SadPanda/.Trash/
    systemDomainMask:  
    networkDomainMask: 


url(for:in:appropriateFor:create:):

applicationDirectory:
    userDomainMask:    /Users/SadPanda/Applications/
    localDomainMask:   /Applications/
    systemDomainMask:  /System/Applications/
    networkDomainMask: /Network/Applications/

demoApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Applications/Demos/
    localDomainMask:   /Applications/Demos/
    systemDomainMask:  /Applications/Demos/
    networkDomainMask: /Network/Applications/Demos/

developerApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Developer/Applications/
    localDomainMask:   /Developer/Applications/
    systemDomainMask:  /Developer/Applications/
    networkDomainMask: /Network/Developer/Applications/

adminApplicationDirectory:
    userDomainMask:    /Users/SadPanda/Applications/Utilities/
    localDomainMask:   /Applications/Utilities/
    systemDomainMask:  /System/Applications/Utilities/
    networkDomainMask: /Network/Applications/Utilities/

libraryDirectory:
    userDomainMask:    /Users/SadPanda/Library/
    localDomainMask:   /Library/
    systemDomainMask:  /System/Library/
    networkDomainMask: /Network/Library/

developerDirectory:
    userDomainMask:    /Users/SadPanda/Developer/
    localDomainMask:   /Developer/
    systemDomainMask:  /Developer/
    networkDomainMask: /Network/Developer/

userDirectory:
    userDomainMask:    ERROR (nilError)
    localDomainMask:   /Users/
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: /Network/Users/

documentationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Documentation/
    localDomainMask:   /Library/Documentation/
    systemDomainMask:  /System/Library/Documentation/
    networkDomainMask: /Network/Library/Documentation/

documentDirectory:
    userDomainMask:    /Users/SadPanda/Documents/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

coreServiceDirectory:
    userDomainMask:    ERROR (nilError)
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  /System/Library/CoreServices/
    networkDomainMask: ERROR (nilError)

autosavedInformationDirectory:
    userDomainMask:    /Users/SadPanda/Library/Autosave Information/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

desktopDirectory:
    userDomainMask:    /Users/SadPanda/Desktop/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

cachesDirectory:
    userDomainMask:    /Users/SadPanda/Library/Caches/
    localDomainMask:   /Library/Caches/
    systemDomainMask:  /System/Library/Caches/
    networkDomainMask: ERROR (nilError)

applicationSupportDirectory:
    userDomainMask:    /Users/SadPanda/Library/Application Support/
    localDomainMask:   /Library/Application Support/
    systemDomainMask:  /Library/Application Support/
    networkDomainMask: /Network/Library/Application Support/

downloadsDirectory:
    userDomainMask:    /Users/SadPanda/Downloads/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

inputMethodsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Input Methods/
    localDomainMask:   /Library/Input Methods/
    systemDomainMask:  /System/Library/Input Methods/
    networkDomainMask: /Network/Library/Input Methods/

moviesDirectory:
    userDomainMask:    /Users/SadPanda/Movies/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

musicDirectory:
    userDomainMask:    /Users/SadPanda/Music/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

picturesDirectory:
    userDomainMask:    /Users/SadPanda/Pictures/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

printerDescriptionDirectory:
    userDomainMask:    ERROR (nilError)
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  /System/Library/Printers/PPDs/
    networkDomainMask: ERROR (nilError)

sharedPublicDirectory:
    userDomainMask:    /Users/SadPanda/Public/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

preferencePanesDirectory:
    userDomainMask:    /Users/SadPanda/Library/PreferencePanes/
    localDomainMask:   /Library/PreferencePanes/
    systemDomainMask:  /System/Library/PreferencePanes/
    networkDomainMask: ERROR (nilError)

applicationScriptsDirectory:
    userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

itemReplacementDirectory:
    userDomainMask:
        /:                                                 /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/TemporaryItems/NSIRD_MyApp_6D4CLt/
        /Volumes/Other/:                                   /Volumes/Other/.TemporaryItems/folders.501/TemporaryItems/NSIRD_MyApp_fdKnpT/
        /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/TemporaryItems/NSIRD_MyApp_bcHf11/
        nil:                                               ERROR (nilError)
    localDomainMask:   ERROR (nilError)
    systemDomainMask:  ERROR (nilError)
    networkDomainMask: ERROR (nilError)

allApplicationsDirectory:
    userDomainMask:    /Users/SadPanda/Applications/
    localDomainMask:   /Applications/
    systemDomainMask:  /System/Applications/Demos/
    networkDomainMask: /Network/Applications/

allLibrariesDirectory:
    userDomainMask:    /Users/SadPanda/Library/
    localDomainMask:   /Library/
    systemDomainMask:  /Developer/
    networkDomainMask: /Network/Library/

trashDirectory:
    userDomainMask:
        /:                                                 /Users/SadPanda/.Trash/
        /Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
        /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
        nil:                                               /Users/SadPanda/.Trash/
    localDomainMask:
        /:                                                 /Users/SadPanda/.Trash/
        /Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
        /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
        nil:                                               /Users/SadPanda/.Trash/
    systemDomainMask:
        /:                                                 /Users/SadPanda/.Trash/
        /Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
        /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
        nil:                                               ERROR (nilError)
    networkDomainMask:
        /:                                                 /Users/SadPanda/.Trash/
        /Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
        /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
        nil:                                               ERROR (nilError)
</pre>
</details>



<p>Note how <code>urls(for:in:)</code> works for every <code><a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory" data-wpel-link="external" target="_blank" rel="external noopener">SearchPathDirectory</a></code> <em>except</em> <code><a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/itemreplacementdirectory" data-wpel-link="external" target="_blank" rel="external noopener">itemReplacementDirectory</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>Also, something is broken regarding <code>applicationScriptsDirectory</code>.  When used with <code>urls(for:in:)</code> you get this output to stdout:</p>



<pre class="wp-block-preformatted">cannot open file at line 49259 of [1b37c146ee]
os_unix.c:49259: (0) open(/private/var/db/DetachedSignatures) - Undefined error: 0</pre>



<p>And if you use it with <code>url(for:in:appropriateFor:create:)</code> you get <em>eight</em> lines of this output to stderr:</p>



<pre class="wp-block-preformatted">This method should not be called on the main thread as it may lead to UI unresponsiveness.</pre>



<p>I haven&#8217;t delved into the implementation to figure out what&#8217;s going on &#8211; apparently that particular <code>SearchPathDirectory</code> has some kind of special code path all of its own, which is doing something it seemingly should not be doing.</p>
</div></div>



<p>Some observations about <code>urls(for:in:appropriateFor:create:)</code>:</p>



<ul class="wp-block-list">
<li>It returns a <em>single</em> URL, even though there are often multiple folders for a given search path.  Note how it <em>never</em> returns a path to a Cryptex folder, for example.<br><br>If you&#8217;re looking for <em>search paths</em> &#8211; the set of folders to search for a resource &#8211; use <code>urls(for:in:)</code> only.</li>



<li>The <code>appropriateFor</code> argument is completely irrelevant to every <code>SearchPathDirectory</code> <em>except</em> <code>itemReplacementDirectory</code> and <code><a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/trashdirectory" data-wpel-link="external" target="_blank" rel="external noopener">trashDirectory</a></code>.</li>



<li>It will only ever return a path inside the App Sandbox for <code><a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdomainmask/1408037-userdomainmask" data-wpel-link="external" target="_blank" rel="external noopener">userDomainMask</a></code> (if it doesn&#8217;t fail by throwing an exception).</li>



<li>It won&#8217;t allow <code>nil</code> as an argument for <code>appropriateFor</code> when targeting <code>itemReplacementDirectory</code>, yet it will allow <code>nil</code> when targeting <code>trashDirectory</code>, but <em>only</em> for <code>userDomainMask</code> and <code>localDomainMask</code>.  I cannot think of any explanation for this inconsistency.<br><br>If you ignore that inconsistency, the <code>SearchPathDomainMask</code> is irrelevant to <code>trashDirectory</code> (as it logically should be &#8211; bin folders are tied to <em>volumes</em>, not apps, users, or computers.</li>



<li>It always throws an exception for <code>itemReplacementDirectory</code> if the <code><a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdomainmask" data-wpel-link="external" target="_blank" rel="external noopener">SearchPathDomainMask</a></code> is not <code>userDomainMask</code>.  This might actually be explicable, even if a bit unintuitive and misguided &#8211; it seems to be presuming that, because you&#8217;re running inside an App Sandbox, you cannot modify any files except the current user&#8217;s.  I&#8217;m not sure that&#8217;s not accurate &#8211; there&#8217;s the possibility of special entitlements and exclusions &#8211; although nor do I know that it&#8217;s not.</li>



<li>Whenever it fails, it throws a <code>nilError</code> exception which is largely useless.  Even the name doesn&#8217;t provide any real insight into what its problem is.<br><br>It&#8217;s unsurprising to me that it does such a poor job, given the rest of the API &#8211; good error messages are a hallmark of good API design, or put conversely, happy path programming produces bad code.</li>
</ul>



<p>I did not explore use of the <code>create</code> argument (I always left it as <code>false</code>).  I didn&#8217;t need to in order to know it has its own problems, too &#8211; the documentation states that it <em>also</em> has inconsistent behaviour, in that it is completely ignored for <code>itemReplacementDirectory</code> (and <em>only</em> that <code>SearchPathDirectory</code>).</p>



<p>The root of all these problems is the API&#8217;s bad design:</p>



<ul class="wp-block-list">
<li><code>itemReplacementDirectory</code> and <code>trashDirectory</code> should not be part of <code>SearchPathDirectory</code>.  They do not behave like any of the other cases; they do not have broadly correct generic values, requiring instead some context regarding the items being replaced or trashed.</li>



<li><code>url(for:in:appropriateFor:create)</code> should be three independent methods &#8211; one of which already exists, in the form of <code>urls(for:in:)</code>.  The other two should handle each of the special cases of <code>itemReplacementDirectory</code> and <code>trashDirectory</code>.  i.e.:</li>
</ul>



<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: #008000">// Existing method</span></span>
<span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">urls</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000"> </span><span style="color: #001080">directory</span><span style="color: #000000">: FileManager.SearchPathDirectory,</span></span>
<span class="line"><span style="color: #000000">          </span><span style="color: #795E26">in</span><span style="color: #000000"> </span><span style="color: #001080">domainMask</span><span style="color: #000000">: FileManager.SearchPathDomainMask) -&gt; [URL]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// New methods</span></span>
<span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">replacementItemFolder</span><span style="color: #000000">(</span><span style="color: #795E26">appropriateFor</span><span style="color: #000000"> </span><span style="color: #001080">url</span><span style="color: #000000">: URL) </span><span style="color: #AF00DB">throws</span><span style="color: #000000">(InvalidURLError) -&gt; URL</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">trashFolder</span><span style="color: #000000">(</span><span style="color: #795E26">appropriateFor</span><span style="color: #000000"> </span><span style="color: #001080">url</span><span style="color: #000000">: URL) </span><span style="color: #AF00DB">throws</span><span style="color: #000000">(InvalidURLError) -&gt; URL</span></span></code></pre></div>



<p><em>So</em> much clearer and easier to use than what we have now.</p>



<p>The two specialised methods still need to throw, because they take <code>URL</code>s which could be invalid, but that&#8217;s now the <em>only</em> reason they might throw (I&#8217;ve used a hypothetical <code>InvalidURLError</code> type to express that formally).</p>



<p>They could arguably be combined into one method (taking an enum that delineates the two cases), but I see little practical value in that &#8211; it&#8217;s simpler and easier to read if they&#8217;re simply distinct methods, even though their shape is similar.  I can&#8217;t imagine any reasonable scenario where you only decide at runtime if you&#8217;re going to replace something or delete it.</p>



<p>☝️ I&#8217;ve removed the <code>create</code> parameter entirely, as I think it presumes too much (e.g. what if you want to create a <em>file</em> with the replacement item URL, not a folder?), though arguably it could be added back in (though if it were, it should <em>actually</em> be obeyed).</p>


<ol class="wp-block-footnotes"><li id="dd2453d4-5a23-4b69-983a-8d13728f39f4">Note that I wrote <em>applicable</em>, not necessarily <em>used</em>.  The distinction is important.  e.g. it makes sense to supply the request headers for a HTTP request to an API which makes such requests, even if the request might be served from a local cache in which case those headers aren&#8217;t actually used, <em>in that instance</em>.<br><br>Another way to frame this is that it&#8217;s a design flaw to have parameters whose utility depends only on the value of other parameters.<br><br>If you can know at compile time that some parameters are unnecessary, then you shouldn&#8217;t be forced to provide them. <a href="#dd2453d4-5a23-4b69-983a-8d13728f39f4-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/bad-api-example-filemanagers-urlforinappropriateforcreate/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7631</post-id>	</item>
		<item>
		<title>Creating files safely in Mac apps</title>
		<link>https://wadetregaskis.com/creating-temporary-files-safely-in-mac-apps/</link>
					<comments>https://wadetregaskis.com/creating-temporary-files-safely-in-mac-apps/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 31 Jan 2024 02:48:27 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[App sandboxing]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[File system]]></category>
		<category><![CDATA[Files]]></category>
		<category><![CDATA[Insecure]]></category>
		<category><![CDATA[mkstemp]]></category>
		<category><![CDATA[mktemp]]></category>
		<category><![CDATA[NSString]]></category>
		<category><![CDATA[O_CREAT]]></category>
		<category><![CDATA[O_EXCL]]></category>
		<category><![CDATA[O_TRUNC]]></category>
		<category><![CDATA[open]]></category>
		<category><![CDATA[OutputStream]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Tested]]></category>
		<category><![CDATA[tmp files]]></category>
		<category><![CDATA[umask]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7603</guid>

					<description><![CDATA[Creating a file is a pretty basic and conceptually simple task, that many applications do (whether they realise it or not &#8211; library code often does this too, at least for temporary files such as caches or for communicating between programs). So you&#8217;d think it&#8217;d be trivial to do correctly. Alas, it is not. ☝️&#8230; <a class="read-more-link" href="https://wadetregaskis.com/creating-temporary-files-safely-in-mac-apps/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Creating a file is a pretty basic and conceptually simple task, that many applications do (whether they realise it or not &#8211; library code often does this too, at least for temporary files such as caches or for communicating between programs).</p>



<p>So you&#8217;d think it&#8217;d be trivial to do correctly.  Alas, it is not.</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 a challenging topic, and I&#8217;ve done my best to research thoroughly and check everything experimentally.  Still, it&#8217;s certainly possible I&#8217;ve made a mistake or overlooked something.  Please let me know of any errors, in the comments at the bottom.<br><br>Also, it&#8217;s a dense topic, so I&#8217;ve tried to highlight (in bold) the most important points.  In case of TL;DR. 🙂</p>
</div></div>



<h1 class="wp-block-heading">What is the danger?</h1>



<p>There are two key things to watch out for when creating files:</p>



<ol class="wp-block-list">
<li><strong>Security flaws due to incorrect use of, or badly designed, file system APIs</strong>.  This is especially a concern for privileged applications (e.g. setuid) or those that ever run with elevated privileges (e.g. via sudo or by admin users).  Unintentional reuse of existing files can be (and <a href="https://nvd.nist.gov/vuln/detail/CVE-2011-4119" data-wpel-link="external" target="_blank" rel="external noopener">has</a> <a href="https://nvd.nist.gov/vuln/detail/CVE-2020-28407" data-wpel-link="external" target="_blank" rel="external noopener">been</a>) the cause of major security vulnerabilities.  <a href="#file-system-races-in-more-detail">See the appendix for more details</a>.<br><br>Another security concern is leaking sensitive information to other programs (or users, on a shared computer).  This can easily happen if files are created in places other programs or users can access, such as shared folders.  The files may be inadvertently created with inappropriately broad permissions (e.g. world-readable) or the parent folder&#8217;s permissions might permit others to change the permissions of the file after the fact even if they don&#8217;t own it (e.g. a world-writable folder <em>without</em> the sticky bit set).</li>



<li><strong>Data loss risks due to inadvertent overwrites or modifications of existing files</strong>.  If you&#8217;re not <em>certain</em> you know what file is already at a given path on disk, <em>and</em> that you should be allowed to overwrite it, then you should not.  Usually, the user has to give <em>explicit</em> permission (e.g. Save dialogs that explicitly ask the user if they intend to overwrite an existing file).<br><br>This risk is greatest for persistent files, e.g. files in your Documents folder.  Those are usually where the user stores their most important data.<br><br>For temporary files the level of danger is generally lower but not zero.  If you&#8217;re using a system-designated temporaries folder, then in principle anything in there is unimportant anyway.  However, randomly mucking with temporary files can still cause data corruption or loss, depending on how those files are used by applications (including your own).  e.g. they might store autosaves of the current document in temporary files, and directly copy / move those files when the user formally saves.  Thus, modifying the temporary file might end up modifying the user&#8217;s actual save file.</li>
</ol>



<h1 class="wp-block-heading">How are these dangers mitigated?</h1>



<h2 class="wp-block-heading">App Sandboxing helps</h2>



<p>As annoying &amp; limiting as <a href="https://developer.apple.com/documentation/security/app_sandbox/" data-wpel-link="external" target="_blank" rel="external noopener">App Sandboxing</a> can be in other regards, it is one of the best single steps an application author can take to improve file security.  For the most part, your application is the only (non-system &amp; non-privileged<sup data-fn="77cbc356-ca3e-4dff-892d-ddf0ca3c31c9" class="fn"><a href="#77cbc356-ca3e-4dff-892d-ddf0ca3c31c9" id="77cbc356-ca3e-4dff-892d-ddf0ca3c31c9-link">1</a></sup>) application that can write within its sandbox, and you&#8217;ll usually be creating files only within your own sandbox.</p>



<h2 class="wp-block-heading">Avoid /private/tmp (a.k.a. /tmp)</h2>



<p><code>/tmp</code> is merely a symlink to <code>/private/tmp</code>.</p>



<p><code>/private/tmp</code> is <em>world-readable</em>: every program on the computer can access its contents.  Thankfully, it&#8217;s not <em>as</em> bad it first appears &#8211; <code>/private/tmp</code> is special in that it has the &#8220;<a href="https://en.wikipedia.org/wiki/Sticky_bit" data-wpel-link="external" target="_blank" rel="external noopener">sticky bit</a>&#8221; set, which imposes some key restrictions on what users may do to each other&#8217;s files (most importantly, that they can&#8217;t delete them even though <code>/tmp</code> is world-writable).</p>



<p>Still, it&#8217;s better to use the tmp folder designated via <code><a href="https://developer.apple.com/documentation/foundation/filemanager" data-wpel-link="external" target="_blank" rel="external noopener">FileManager</a>.<a href="https://developer.apple.com/documentation/foundation/filemanager/1409234-default" data-wpel-link="external" target="_blank" rel="external noopener">default</a>.<a href="https://developer.apple.com/documentation/foundation/filemanager/1642996-temporarydirectory" data-wpel-link="external" target="_blank" rel="external noopener">temporaryDirectory</a></code> or <code><a href="https://developer.apple.com/documentation/foundation/url" data-wpel-link="external" target="_blank" rel="external noopener">URL</a>.<a href="https://developer.apple.com/documentation/foundation/url/3988477-temporarydirectory" data-wpel-link="external" target="_blank" rel="external noopener">temporaryDirectory</a></code>.  Though the location and security properties of that folder varies:</p>



<ul class="wp-block-list">
<li>If App Sandboxing is in use, it&#8217;s an application-specific folder like <code>/Users/SadPanda/Library/Containers/com.sadpanda.MyApp/Data/tmp/</code>.  No other (unprivileged) application can access that folder.</li>



<li>If App Sandboxing is not in use, it&#8217;s a user-specific folder like <code>/var/folders/v3/8anbad56df64adf3_35gj346jg13v19a/T/</code> (the exact path varies between user accounts and computers, by design for security).  That folder is still insecure &#8211; it is accessible to all programs of the same user &#8211; but at least you don&#8217;t have to worry about other [unprivileged] users.</li>
</ul>



<p>If used correctly, <code>/private/tmp</code> has essentially the same properties as the user-specific temporary folders.  Using it correctly starts with ensuring files are created with no group or &#8216;other&#8217; privileges, but the full complexities are beyond the scope of this post.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ <code>/private/tmp</code> is not accessible when App Sandboxing is enabled.</p>
</div></div>



<h2 class="wp-block-heading" id="consider-setting-a-restrictive-umask">Consider setting a restrictive umask</h2>



<p>When you create a file on any Linux or Unix system, such as macOS, its permissions are set based on a combination of the specific API used and the process-wide <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/umask.2.html" data-wpel-link="external" target="_blank" rel="external noopener">umask</a></code>.  Good APIs will require you to specify the initial privileges of the file, but some do not (e.g. <code>fopen</code>) and instead use some arbitrary default, such as creating files as readable and writable <em>by anyone</em> (<a href="https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation" data-wpel-link="external" target="_blank" rel="external noopener">0666</a>).  That is bad &#8211; usually you <em>don&#8217;t</em> want your files readable by any other users.</p>



<p>While you should generally avoid APIs that don&#8217;t provide proper control over file permissions, you realistically may not even <em>know</em> if you&#8217;re using such APIs because they might be employed by library code you don&#8217;t control (including Apple&#8217;s).</p>



<p>While it&#8217;s possible that the parent folder(s) will protect a given file (by preventing access to their contents by other users), it&#8217;s safest to not assume that.</p>



<p>The umask can help mitigate the dangers of bad APIs by specifying which privileges are <em>not</em> to be granted by default<sup data-fn="afb3f9ba-f914-4dd9-803c-2aad08a6800a" class="fn"><a href="#afb3f9ba-f914-4dd9-803c-2aad08a6800a" id="afb3f9ba-f914-4dd9-803c-2aad08a6800a-link">2</a></sup>.</p>



<p>The umask defaults to denying write access to groups and other users (0022), which is a start but still not good &#8211; read access to private user data is still a concern.</p>



<p>However &#8211; beyond the overly permissive default setting &#8211; there at two problems with umask:</p>



<ol class="wp-block-list">
<li>It is a process-wide global.  Modifications to it apply to all threads in your process, which makes it dangerous to modify.  e.g. you might be creating a particularly sensitive file and need the umask to be 0177, so you set it to that, but before you actually get to execute the file creation another thread sets the umask to 0000 because <em>it</em> wants to create an otherwise unrelated file that&#8217;s world-writable.<br><br>So unfortunately the only safe way to use umask is to set it very early in process launch before any additional threads are created<sup data-fn="9dedd292-995e-479b-ba90-8d2d787e436e" class="fn"><a href="#9dedd292-995e-479b-ba90-8d2d787e436e" id="9dedd292-995e-479b-ba90-8d2d787e436e-link">3</a></sup>.<br><br>You can <em>try</em> to enforce your own mutual exclusion around umask, e.g. with a global lock, but beware of 3rd party code (including Apple&#8217;s) that might modify umask without following your mutual exclusion protocol.  <em>Generally</em> umask isn&#8217;t modified often, so this arguably <em>is</em> possible to achieve in practice, but <em>proving</em> that there are no missed calls to <code>umask</code> can be practically impossible.</li>



<li>Lots of existing code, that you might be unwittingly using via libraries or frameworks, assumes the umask remains at its default.  Thus, making it more restrictive might break things in ways &amp; places that are difficult to foresee.<br><br>In general the more complicated your program, that more of a concern this is.  e.g. most command-line programs can modify umask without causing issues, but GUI programs pull in a <em>lot</em> of framework code and functionality, some of which might implicitly rely on certain umask bits.  Unfortunately the only way to find out is experimentally.</li>
</ol>



<p>So umask is not a panacea.  Still, setting a restrictive umask improves security if you can get away with it.</p>



<h2 class="wp-block-heading">Correctly use the right file creation APIs</h2>



<p>In short, creation of files needs to (generally) not extend nor replace existing files, and ensure correct initial file permissions.</p>



<p>There are quite a lot of APIs for creating a file in macOS.  I&#8217;m going to enumerate only the most common ones provided by the system libraries &amp; frameworks.</p>



<h3 class="wp-block-heading"><code>⚠️</code> <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/open.2.html" data-wpel-link="external" target="_blank" rel="external noopener">open</a></code></h3>



<p>Nominally this is <em>the</em> low-level API for opening (existing or new) files, although as you&#8217;ll see later it&#8217;s not actually the only one.</p>



<p>It has a flags parameter, which is how you tell it what to <em>actually</em> do, between opening existing files, creating new ones, etc.  Two of the most important flags are <code>O_CREAT</code> and <code>O_EXCL</code>.  <code>O_CREAT</code> tells <code>open</code> to create the file.  If <code>O_EXCL</code> is specified, <code>open</code> will fail if a file already exists at the target location.  If <code>O_EXCL</code> is <em>not</em> specified, <code>O_CREAT</code> is interperted as &#8220;create the file <em>if necessary</em>&#8221; &#8211; meaning, it will actually open the existing file if it exists, and <em>not</em> create a new file.</p>



<p><strong>You should almost never use <code>O_CREAT</code> without <code>O_EXCL</code>.</strong>  If you really do intend to overwrite the existing file, then it&#8217;s safer to remove the existing file first (e.g. with <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/unlink.2.html" data-wpel-link="external" target="_blank" rel="external noopener">unlink</a></code>), and <em>then</em> create your new file (with <code>O_EXCL</code> to ensure no other file appears at the target location in the interim).  That way you ensure the new file has your expected location (not a symlink) and attributes (e.g. file permissions).</p>



<p>Note that <code>O_EXCL</code> will fail if the target is a symlink, so you don&#8217;t need to specify <code>O_NOFOLLOW</code> (although it doesn&#8217;t hurt).</p>



<p><code>open</code> requires you to specify the new file&#8217;s permissions if you use the <code>O_CREAT</code> option (and respects the umask), which is good as it makes you think about what the permissions should be, and lets you set them to something suitable for each use case.  These permissions are <em>only</em> applied to <em>new</em> files, so in a nutshell <strong>you cannot rely on them if you don&#8217;t use <code>O_EXCL</code>.</strong></p>



<p>For that reason also, <strong>you typically should not use <code>O_TRUNC</code>.</strong>  If you don&#8217;t need the contents of the existing file, <em>delete the file first</em>.  &#8220;Reusing it&#8221; via <code>O_TRUNC</code> <em>also</em> reuses its permissions and other attributes, which might not be set correctly for your intentions (e.g. a malicious program might have pre-created the file as world-readable, even though you intend it to be readable only by the current user and have otherwise done the right things such as set the umask to ensure that).</p>



<p>One reason you <em>might</em> validly use <code>O_TRUNC</code> is if you anticipate there being multiple <em>hard</em> links to the file, and you want to modify the file as seen by the other links too.  This is very rare.  Be <em>sure</em> that&#8217;s necessary before you use <code>O_TRUNC</code>, and consider putting validations in place to ensure the file you&#8217;ve just opened for reuse has the expected permissions and attributes (e.g. via <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fstat.2.html" data-wpel-link="external" target="_blank" rel="external noopener">fstat64</a></code> and similar APIs that operate on the file descriptor &#8211; do <em>not</em> use <code>lstat</code> or any other path-based APIs).</p>



<h3 class="wp-block-heading">⚠️ <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/fopen.3.html" data-wpel-link="external" target="_blank" rel="external noopener">fopen</a></code></h3>



<p>This is essentially just a wrapper atop <code>open</code>, with the &#8220;w&#8221; and &#8220;a&#8221; flags mapping to <code>O_CREAT</code> (essentially) and the &#8220;x&#8221; flag mapping to <code>O_EXCL</code>.  The same rules apply, so <strong>you should generally never use &#8220;w&#8221; without the &#8220;x&#8221; flag as well, nor &#8220;a&#8221; without &#8220;+&#8221;.</strong></p>



<p><code>fopen</code> does <em>not</em> let you specify the permissions of the created file, instead defaulting to 0666 (readable &amp; writable by <em>everyone</em>) which is a terrible default.  It does respect umask, so by default it will create files as 0644 which is marginally better.  But, <a href="#consider-setting-a-restrictive-umask">as discussed previously</a>, it is difficult to guarantee what the umask actually is at any particular point in time.  So <strong>in general you should prefer <code>open</code> instead of <code>fopen</code></strong>.</p>



<h3 class="wp-block-heading">❌ <code><a href="https://developer.apple.com/documentation/foundation/outputstream/1416367-init" data-wpel-link="external" target="_blank" rel="external noopener">OutputStream(toFileAtPath:append:)</a></code> &amp; friends</h3>



<p>This ultimately (when you call the <code>open</code> method) calls <code>open</code> with the flags <code>O_WRONLY | O_CREAT</code> (plus <code>O_TRUNC</code> if the append argument is false).  As such it will <em>always</em> modify an existing file if present.</p>



<p>It also does not let you specify the permissions of the new file, instead defaulting arbitrarily to 0666 (but respecting umask, at least).</p>



<p><strong>It is an unsafe API and should not be used.</strong></p>



<h3 class="wp-block-heading">⚠️ <code><a href="https://developer.apple.com/documentation/foundation/nsdata" data-wpel-link="external" target="_blank" rel="external noopener">NSData</a>.<a href="https://developer.apple.com/documentation/foundation/nsdata/1414800-write" data-wpel-link="external" target="_blank" rel="external noopener">write(toFile:options:)</a></code> &amp; friends</h3>



<p>This family of methods all ultimately call <code><a href="https://opensource.apple.com/source/xnu/xnu-2050.9.2/libsyscall/wrappers/open_dprotected_np.c.auto.html" data-wpel-link="external" target="_blank" rel="external noopener">open_dprotected_np</a></code> (<a href="https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/vfs/vfs_syscalls.c#L4517" data-wpel-link="external" target="_blank" rel="external noopener">implementation</a>), a variant of <code>open</code> specific to Apple platforms which adds Apple-specific functionality regarding file encryption and isolation (see e.g. the protection-related flags within <code><a href="https://developer.apple.com/documentation/foundation/nsdata/writingoptions" data-wpel-link="external" target="_blank" rel="external noopener">NSData.WritingOptions</a></code>).  It takes the same flags as the regular <code>open</code>, and <code>write(toFile:options:)</code> by default uses <code>O_CREAT | O_TRUNC</code>.  If you use the <code>withoutOverwriting</code> option, it adds <code>O_EXCL</code>.  <strong>So you should usually use <code>withoutOverwriting</code>.</strong></p>



<p>If you use the <code>atomic</code> flag, it creates the file using a private function <code>_NSCreateTemporaryFile_Protected</code> which obtains a file path using <a href="#mktemp"><code>mktemp</code> ⚠️</a> and calls <code>open_dprotected_np</code> with the flags <code>O_CREAT | O_EXCL | O_RDWR</code><sup data-fn="1d64125f-81d0-4bb5-8090-ac0b5d942d7d" class="fn"><a href="#1d64125f-81d0-4bb5-8090-ac0b5d942d7d" id="1d64125f-81d0-4bb5-8090-ac0b5d942d7d-link">4</a></sup>.  Once it has created &amp; written to that temporary file, it uses <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/rename.2.html" data-wpel-link="external" target="_blank" rel="external noopener">rename</a></code> to move it into place.  <code>rename</code> just silently deletes any existing file at the destination path.  So it&#8217;s safe against race attacks, but susceptible to data loss bugs from unintentionally overwriting existing files.  As such, <strong>use <code>atomic</code> only with caution</strong>.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>⚠️ If you add <code>withoutOverwriting</code> on top of <code>atomic</code>, the call crashes your program!  It throws an Objective-C exception &#8211; <code>NSInvalidArgumentException</code>.  Swift does not support Objective-C exceptions (<a href="https://forums.swift.org/t/pitch-a-swift-representation-for-thrown-and-caught-exceptions/54583/3" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s fundamentally unsafe to pass Objective-C exceptions up to Swift functions</a>) so you have to <a href="https://stackoverflow.com/questions/32758811/catching-nsexception-in-swift/36454808#36454808" data-wpel-link="external" target="_blank" rel="external noopener">use an Objective-C helper function as an intermediary</a>.</p>



<p>This is a particularly unfortunate limitation &#8211; even aside from the crashiness &#8211; because using both options together is highly desirable and it <em>should</em> in principle work &#8211; the implementation can simply use <code><a href="https://www.manpagez.com/man/2/renamex_np/osx-10.12.3.php" data-wpel-link="external" target="_blank" rel="external noopener">renamex_np</a></code> instead with the flag <code>RENAME_EXCL</code>.</p>



<p>FB13568491.</p>
</div></div>



<p>It also does not let you specify the permissions of the new file, instead defaulting arbitrarily to 0666 (but respecting umask, at least).  <strong>For files containing sensitive data (such as private user data), this API should generally not be used.</strong></p>



<h3 class="wp-block-heading">❌ <code><a href="https://developer.apple.com/documentation/foundation/nsstring" data-wpel-link="external" target="_blank" rel="external noopener">NSString</a>.<a href="https://developer.apple.com/documentation/foundation/nsstring/1407654-write" data-wpel-link="external" target="_blank" rel="external noopener">write(toFile:atomically:encoding:)</a></code> &amp; friends</h3>



<p>These are essentially just wrappers over <code>NSData.write(toFile:options:)</code> &amp; friends, where the <code>atomically</code> argument maps to the <code>atomic</code> option.  They provide no way to use the <code>withoutOverwriting</code> option, so they <strong>should not be used in most cases.</strong></p>



<h2 class="wp-block-heading">Use randomised names for transient files</h2>



<p>Whenever the file name doesn&#8217;t actually matter &#8211; i.e. it&#8217;s not chosen by the user and isn&#8217;t pre-defined by some system requirement &#8211; it&#8217;s best to use a randomised name.  This serves two purposes:</p>



<ol class="wp-block-list">
<li>If your code has any bugs that allow it to erroneously overwrite existing files, using random names at least makes it a lot less likely that you&#8217;ll trigger those bugs, by greatly reducing the odds of using the same name twice.</li>



<li>It makes it harder (if not impossible) for attackers to predict the file names, and therefore to attack them.</li>
</ol>



<p>There are many ways to obtain a randomised name, but it&#8217;s wise to use one of the canonical methods detailed below.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ If you do care about the file name, but not its location, you can use these APIs to create a randomly-named temporary <em>folder</em>, and then create your file within there.</p>



<p>This can be handy for e.g. preparing a file URL to be dragged from your app, where you want the file to have a proper name but don&#8217;t care (per se) where it lives.</p>
</div></div>



<h3 class="wp-block-heading" id="mktemp">⚠️ <code><code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/mktemp.3.html" data-wpel-link="external" target="_blank" rel="external noopener">mktemp</a></code></code></h3>



<p><code>mktemp</code> is infamously a source of security vulnerabilities, because it doesn&#8217;t actually create the file (or folder) but merely returns a path.  The caller is responsible for securely creating the file (or folder).  This <em>can</em> be done safely &#8211; by following the guidance earlier in this post, in particular around the <code>O_EXCL</code> <code>open</code> flag &#8211; but it&#8217;s easy to screw up.</p>



<p><strong>Generally it&#8217;s preferable to use <code>mkstemp</code> / <code>mkdtemp</code> &amp; friends</strong>.  Unfortunately, if you want to use higher-level file APIs on Apple&#8217;s platforms that&#8217;s a problem because most of those APIs don&#8217;t support initialisation from a file descriptor, only a path (or equivalently, URL).  So you may find that you still need to use <code>mktemp</code>.  If so, be very careful about how you actually create the files &amp; folders.</p>



<h3 class="wp-block-heading">✅ <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/mkstemp.3.html" data-wpel-link="external" target="_blank" rel="external noopener">mkstemp</a></code> / <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/mkstemp.3.html" data-wpel-link="external" target="_blank" rel="external noopener">mkdtemp</a></code> &amp; friends</h3>



<p>These replacements for <code>mktemp</code> actually create the file / folder (respectively), in a safe way.</p>



<p><code>mkstemp</code> creates the file and returns the corresponding open file descriptor, instead of merely returning a path and leaving it to the caller to get the creation step right.  It will never overwrite (nor open) an existing file.  It also sets the file&#8217;s initial permissions to 0600 (i.e. read-writable but only by the current user), which is a pretty safe default.</p>



<p><code>mkdtemp</code> still returns a path (not a file descriptor), like <code>mktemp</code>, but it ensures the folder was actually created (and not previously existent) with the permissions set to 0700 (i.e. usable only by the current user).</p>



<p>Neither make any guarantees regarding security &#8211; or lack thereof &#8211; due to parent folder permissions.  The caller still needs to ensure necessary security protections for those (whether by choosing a suitable system-provided folder, or manually checking permissions and symlinks in the path).  <code><code><code><a href="https://developer.apple.com/documentation/foundation/url" data-wpel-link="external" target="_blank" rel="external noopener">URL</a>.<a href="https://developer.apple.com/documentation/foundation/url/3988477-temporarydirectory" data-wpel-link="external" target="_blank" rel="external noopener">temporaryDirectory</a></code></code></code> is a good starting point.</p>



<p>Using these from Swift is a little awkward because they mutate their primary argument (the path template), but <a href="https://github.com/apple/swift-corelibs-foundation/blob/dbca8c7ddcfd19f7f6f6e1b60fd3ee3f748e263c/Sources/Foundation/NSPathUtilities.swift#L774" data-wpel-link="external" target="_blank" rel="external noopener">here&#8217;s an example</a>.  Once you have the file descriptor (in the <code>mkstemp</code> case) you can wrap it in e.g. a <code><a href="https://developer.apple.com/documentation/foundation/filehandle" data-wpel-link="external" target="_blank" rel="external noopener">FileHandle</a></code> and work with it at a slightly higher level.</p>



<h3 class="wp-block-heading"><code><a href="https://developer.apple.com/documentation/foundation/filemanager" data-wpel-link="external" target="_blank" rel="external noopener">⚠️</a></code> <code><a href="https://developer.apple.com/documentation/foundation/filemanager" data-wpel-link="external" target="_blank" rel="external noopener">FileManager</a>.<a href="https://developer.apple.com/documentation/foundation/filemanager/1409234-default" data-wpel-link="external" target="_blank" rel="external noopener">default</a>.<a href="https://developer.apple.com/documentation/foundation/filemanager/1407693-url" data-wpel-link="external" target="_blank" rel="external noopener">url(for:in:appropriateFor:create:)</a></code></h3>



<p>In its special mode where &#8216;for&#8217; is <code><a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/itemreplacementdirectory" data-wpel-link="external" target="_blank" rel="external noopener">.itemReplacementDirectory</a></code> and &#8216;in&#8217; is <a href="https://developer.apple.com/documentation/foundation/filemanager/searchpathdomainmask/1408037-userdomainmask" data-wpel-link="external" target="_blank" rel="external noopener">.<code>userDomainMask</code></a>, this behaves like <code>mkdtemp</code>; it creates a randomly-named folder and returns the URL to it (note that it completely ignores the &#8216;create&#8217; argument in this case &#8211; there is no way to have it <em>not</em> create the temporary folder).</p>



<p>This API seems to presume the use of App Sandboxing to mitigate its problems &#8211; and when App Sandboxing is enabled, it&#8217;s the best way to obtain a temporary file or folder path, though only if you use a suitable value for the &#8216;appropriateFor&#8217; parameter, such as <code><code><a href="https://developer.apple.com/documentation/foundation/url" data-wpel-link="external" target="_blank" rel="external noopener">URL</a>.<a href="https://developer.apple.com/documentation/foundation/url/3988477-temporarydirectory" data-wpel-link="external" target="_blank" rel="external noopener">temporaryDirectory</a></code></code><sup data-fn="a6f54a24-4596-4700-8d50-786942903c54" class="fn"><a href="#a6f54a24-4596-4700-8d50-786942903c54" id="a6f54a24-4596-4700-8d50-786942903c54-link">5</a></sup>.  When App Sandboxing is enabled, that will result in a path <em>inside</em> the sandbox, which is the most secure place an unprivileged application can use.</p>



<p>However, if instead a URL is provided which points to a different volume, it returns a path to a user-specific temporary folder on that volume, e.g. <code>/Volumes/Example/.TemporaryItems/folders.501/TemporaryItems/</code>.  While that does exclude (unprivileged) other users, it&#8217;s still a big step down from a location inside the app&#8217;s sandbox.</p>



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



<h1 class="wp-block-heading" id="file-system-races-in-more-detail">Appendix: File system races in more detail</h1>



<p>Generally-speaking, the file system is a shared resource.  Multiple programs can access it simultaneously with no coordination required<sup data-fn="41b5a8b5-6e90-4a7b-b4c2-8f687acd6914" class="fn"><a href="#41b5a8b5-6e90-4a7b-b4c2-8f687acd6914" id="41b5a8b5-6e90-4a7b-b4c2-8f687acd6914-link">6</a></sup> between them.  That opens the door for races &#8211; where the state of the world changes in-between file system operations that a program might mistakenly assume are atomic.</p>



<p>In general, any <em>single</em> call to a low-level file system API &#8211; e.g. <code>open</code> &#8211; is atomic.  Most such APIs correspond to a single syscall into the kernel, and the operation inside the kernel is wrapped inside a lock (conceptually if not also literally).</p>



<p>Conversely, any operation that takes multiple calls to a file system API is <em>never</em> atomic.</p>



<p>A textbook security vulnerability arises when you do something like:</p>



<ol class="wp-block-list">
<li>Make up some random file name (e.g. with <code>mktemp</code>).</li>



<li>Check that it doesn&#8217;t exist (<em>one</em> syscall), and see that it doesn&#8217;t.</li>



<li>Write to that file (<em>a separate syscall</em>).</li>
</ol>



<p>A malicious program could inject its own file in-between steps two and three &#8211; or even more dangerously, a symlink &#8211; and cause your program to overwrite something (many file APIs will automatically follow symlinks and open existing files, if not used correctly as detailed in this post).</p>



<p>The classic concern in this regard is with privileged programs that have the ability to overwrite sensitive files, e.g. <code>/etc/passwd</code>.  Tricking them into doing so can cause major damage to the system (e.g. nobody can login anymore!) in the <em>best</em> case, and in the worst case &#8211; where the attacker can also influence the contents of the file, or those contents are conveniently just what the attacker wants &#8211; they might be able to implement a more subtle attack that doesn&#8217;t merely break the system but instead e.g. changes the root password, giving them superuser access to the computer.</p>



<p>Even for unprivileged applications, it can still be a concern.  e.g. they might be tricked into writing a bunch of private user data into a shared location from where the attacker can exfiltrate it.</p>



<h1 class="wp-block-heading">Appendix: File writing APIs that cannot create new files</h1>



<p>These APIs are nominally irrelevant since they can&#8217;t be used to create new files, but it can be useful to <em>know</em> that fact, for use in converse scenarios where you do <em>not</em> want to create a file.</p>



<h3 class="wp-block-heading"><code><a href="https://developer.apple.com/documentation/foundation/filehandle/1414405-init" data-wpel-link="external" target="_blank" rel="external noopener">FileHandle(forWritingAtPath:)</a></code></h3>



<p>…ultimately calls <code>[[NSConcreteFileHandle alloc] initWithPath:… flags:0x1 createMode:0 error:nil]</code>, which turns the path string into a URL using <code>-[NSURL fileURLWithPath:]</code> and calls <code>-[NSConcreteFileHandle initWithURL:flags:createMode:error:]</code>, which calls <code>_NSOpenFileDescriptor</code> to do the actual file system calls.  That calls <code>open</code> with <em>only</em> the flag <code>O_WRONLY</code>; it does not pass <code>O_CREAT</code> nor <code>O_EXCL</code>.  So <code>FileHandle</code> cannot create new files (which I find a bit unintuitive, as nothing in the name really suggests that limitation).</p>



<h3 class="wp-block-heading"><code><a href="https://developer.apple.com/documentation/foundation/nsdata/1411145-init" data-wpel-link="external" target="_blank" rel="external noopener">NSData(contentsOfFile:options:)</a></code> &amp; friends</h3>



<p>…ultimately call <code>open</code> with no flags (meaning they can only <em>read</em> existing files, not even modify them).  So again, cannot create new files.  Which is perhaps implied and obvious from the name, but it&#8217;s good to be certain.</p>


<ol class="wp-block-footnotes"><li id="77cbc356-ca3e-4dff-892d-ddf0ca3c31c9">In principle system programs &amp; privileged (e.g. root &amp; admin) programs should be defended against too, but it&#8217;s often impractically difficult to do so, and beyond the scope of this post to try to explain how.<br><br>Root is of course the most impractical to defend against &#8211; even though the root user is <em>not</em> a traditional &#8220;God&#8221; user on macOS, Apple&#8217;s nerfing of root is designed to protect <em>Apple&#8217;s</em> programs, not yours.  Root (and admin users) can ultimately still access any files your application(s) create and there&#8217;s nothing you can do about it (other than potentially through orthogonal protections, such as encryption). <a href="#77cbc356-ca3e-4dff-892d-ddf0ca3c31c9-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="afb3f9ba-f914-4dd9-803c-2aad08a6800a">It is of course possible for libraries to override the umask, but that would be <em>particularly</em> foul of them and none that I&#8217;ve surveyed do that, thankfully. <a href="#afb3f9ba-f914-4dd9-803c-2aad08a6800a-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="9dedd292-995e-479b-ba90-8d2d787e436e">This can be hard to guarantee, in a non-trivial program.  You can use an assertion or precondition on the return value of <code><a href="https://github.com/apple-oss-distributions/libpthread/blob/d8c4e3c212553d3e0f5d76bb7d45a8acd61302dc/src/pthread.c#L943" data-wpel-link="external" target="_blank" rel="external noopener">pthread_is_threaded_np</a></code> to help ensure you&#8217;re modifying umask before additional threads are created. <a href="#9dedd292-995e-479b-ba90-8d2d787e436e-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="1d64125f-81d0-4bb5-8090-ac0b5d942d7d">It&#8217;s not apparent to me why it needs <em>read</em> access to the file as well &#8211; that appears to be a bug. <a href="#1d64125f-81d0-4bb5-8090-ac0b5d942d7d-link" aria-label="Jump to footnote reference 4">↩︎</a></li><li id="a6f54a24-4596-4700-8d50-786942903c54">I&#8217;m not sure what happens if the App Sandbox container is not on the boot volume. <a href="#a6f54a24-4596-4700-8d50-786942903c54-link" aria-label="Jump to footnote reference 5">↩︎</a></li><li id="41b5a8b5-6e90-4a7b-b4c2-8f687acd6914">There are various mechanism for <em>voluntary</em> coordination, such as <code><a href="https://developer.apple.com/documentation/foundation/nsfilecoordinator" data-wpel-link="external" target="_blank" rel="external noopener">NSFileCoordinator</a></code> and <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/flock.2.html" data-wpel-link="external" target="_blank" rel="external noopener">flock</a></code>, but programs are not required to use them (and malicious programs happily won&#8217;t). <a href="#41b5a8b5-6e90-4a7b-b4c2-8f687acd6914-link" aria-label="Jump to footnote reference 6">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/creating-temporary-files-safely-in-mac-apps/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7603</post-id>	</item>
	</channel>
</rss>
