<?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>Undocumented &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/undocumented/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Thu, 22 Jan 2026 15:56:54 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>Undocumented &#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>Recording audio to an iPhone via a Tascam Portacapture X8</title>
		<link>https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/</link>
					<comments>https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Jan 2026 06:04:04 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[audio recording]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Tascam Portacapture X8]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[USB audio]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8672</guid>

					<description><![CDATA[It is possible to use the Tascam Portacapture X8 as an ADC, input converter, and mixing board for an iPhone, but there&#8217;s a few things you&#8217;ll need to know. Thankfully, any USB-C or USB-C-to-Lightning cable will do You just connect one end of the USB-C cable to the Portacapture X8 (to its built-in USB port)&#8230; <a class="read-more-link" href="https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>It <em>is</em> possible to use the Tascam Portacapture X8 as an ADC, input converter, and mixing board for an iPhone, but there&#8217;s a few things you&#8217;ll need to know.</p>



<h3 class="wp-block-heading">Thankfully, any USB-C or USB-C-to-Lightning cable will do</h3>



<p>You just connect one end of the USB-C cable to the Portacapture X8 (to its built-in USB port) and the other to your iPhone (USB-C or Lightning work, as suits your particular model of iPhone).</p>



<p>The Portacapture X8 can both record audio via USB and output it.  It will automatically output it to any connected device that accepts it.  As far as I can tell it uses the master track mix (there doesn&#8217;t seem to be any way to send multiple tracks simultaneously &#8211; I assume the USB audio protocol only allows a plain stereo transmission).</p>



<h3 class="wp-block-heading">You must use 48kHz sampling</h3>



<p>If you don&#8217;t, you&#8217;ll quickly get an error dialog on the Portacapture X8 saying &#8220;USB FS Mismatch&#8221;.  That&#8217;s its daft way of trying to say that the receiving USB device won&#8217;t accept the sampling frequency it&#8217;s outputting.  Why it can&#8217;t have a coherent error message, we&#8217;ll probably never know.</p>



<p>You can set the sampling rate in the &#8220;General Settings&#8221; app, under &#8220;Rec Settings&#8221; (along with the file format and bit depth / rate, though it doesn&#8217;t seem to matter what those are set to w.r.t. iPhone compatibility &#8211; they affect only recording to the microSD card).</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img fetchpriority="high" decoding="async" width="6034" height="8044" src="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch.webp" alt="" class="wp-image-8674" style="width:500px" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch.webp 6034w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-192x256.webp 192w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-768x1024.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-1536x2048.webp 1536w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-192x256@2x.webp 384w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-1536x2048@2x.webp 3072w" sizes="(max-width: 6034px) 100vw, 6034px" /></figure>
</div>


<h3 class="wp-block-heading">You don&#8217;t have to use the iPhone to provide power</h3>



<p>By default the Portacapture X8 will try to use the iPhone to provide power, rather than its batteries. But it doesn&#8217;t trust USB power sources &#8211; it will ask, via a dialog, &#8220;Is the AC adapter 1.5A or more?&#8221;. On USB-C iPhones you <em>can</em> power the Portacapture X8 over USB &#8211; by selecting &#8220;Yes&#8221; &#8211; although it will drain your iPhone&#8217;s battery.  But whichever option you choose, the Portacapture X8 will then refuse to provide phantom power to your mics. If you&#8217;re not using phantom power then no worries, but if you are you must change the Portacapture&#8217;s settings &#8211; in &#8220;General Settings&#8221; app, under &#8220;Power/Display&#8221;, you must set &#8220;Power Source Select&#8221; to &#8220;Battery&#8221; instead of &#8220;Auto&#8221;. That will essentially turn off the Portacapture&#8217;s desire for power from USB, leaving USB as an audio channel only.  With it powering itself from its batteries, the phantom outputs will work like normal.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img decoding="async" width="6034" height="8044" src="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more.webp" alt="" class="wp-image-8673" style="width:500px" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more.webp 6034w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-192x256.webp 192w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-768x1024.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-1536x2048.webp 1536w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-192x256@2x.webp 384w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-1536x2048@2x.webp 3072w" sizes="(max-width: 6034px) 100vw, 6034px" /></figure>
</div>


<h3 class="wp-block-heading">You don&#8217;t have to record on the Portacapture X8</h3>



<p>It passes audio through to the iPhone automatically, even if you&#8217;re not actively recording.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Note: I vaguely recall having some issues with this <em>not</em> happening if you change some settings… it&#8217;s possible that things like &#8220;Pre Rec&#8221;, &#8220;Auto Rec&#8221;, or &#8220;Rec Pause&#8221; affect this.  I have all those off and it works as I&#8217;ve described.</p>
</div></div>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-1536x2048.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8672</post-id>	</item>
		<item>
		<title>Backblaze seemingly does not support files greater than 1 TB</title>
		<link>https://wadetregaskis.com/backblaze-seemingly-does-not-support-files-greater-than-1-tb/</link>
					<comments>https://wadetregaskis.com/backblaze-seemingly-does-not-support-files-greater-than-1-tb/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 02 Jan 2025 23:27:59 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Backblaze]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8475</guid>

					<description><![CDATA[For nearly a month now, Backblaze has been fixated on a particular file of mine, that happens to be over 1 TB in size. Backblaze seemingly uploads it completely, but then on the next backup it uploads it again, even though it has not changed (in eight years!). Ad infinitum. Using their Explainfile tool to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/backblaze-seemingly-does-not-support-files-greater-than-1-tb/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>For nearly a month now, Backblaze has been fixated on a particular file of mine, that happens to be over 1 TB in size.  Backblaze seemingly uploads it completely, but then on the next backup it uploads it again, even though it has not changed (in eight years!).  Ad infinitum.</p>



<p>Using their <a href="https://www.backblaze.com/computer-backup/docs/use-explainfile-to-diagnose-backup-issues-mac" data-wpel-link="external" target="_blank" rel="external noopener">Explainfile</a> tool to dig into the log files, the clue seems to be:</p>



<pre class="wp-block-preformatted">  - line 288 - 2024-12-16 16:16:17 0000000646 - ERROR: UpdateBzDoneRegardingFlsToBeExp - Z_B_TOO_MANY_CHUNKS bz_done_ line chunk related, numBytesInLargeFile=1099512156951, totNumChunks=104858, bz_done_line_is: 5	! …<br>  - line 522 - 2024-12-16 16:18:46 0000000646 - ERROR - bz_done_ INCONSISTENCY_FOUND - 20241216161846 - BadBadBadChunkRecord hexAsciiVal=0x78 - AfterBzdoneLargeFileAnalysis: chunkSeq=100001, highestChunkSeqSeen=104857, fileIdOfLargeFile=00000000002c53cd, dateTimeOfLargeFile=20231217091843, XYXBXXX_FILE_NAME: …</pre>



<p>Admittedly I&#8217;m guessing somewhat, since that&#8217;s a rather reader-hostile log message, but the combination of the <code><strong>Z_B_TOO_MANY_CHUNKS</strong></code> error mnemonic and <code><strong>chunkSeq=100001</strong></code> (because of its proximity to the arbitrary round number 100,000) strongly suggests that Backblaze is imposing a 100,000 chunk limit. Since <a href="https://www.backblaze.com/computer-backup/docs/file-sizes" data-wpel-link="external" target="_blank" rel="external noopener">chunks are 10 MB each</a>, that&#8217;s exactly 1 TB.</p>



<p>This is unequivocally at odds with what they claim repeatedly on their website, on pages like <a href="https://www.backblaze.com/cloud-backup/features/what-gets-backed-up" data-wpel-link="external" target="_blank" rel="external noopener">What Backblaze Backs Up</a> and <a href="https://www.backblaze.com/computer-backup/docs/file-sizes" data-wpel-link="external" target="_blank" rel="external noopener">File Sizes</a>.</p>



<p>It&#8217;s not clear to me why this is suddenly a problem; is this a newly-imposed limit?  It&#8217;s possible that a month ago I removed some exclusion on the file, but I don&#8217;t remembering doing that and I can see no reason why I would have excluded it to begin with.  If it is newly imposed, that would imply it&#8217;s also <em>retroactive</em> &#8211; that Backblaze actually <em>deleted</em> the existing backup of the file from their servers, thus causing the client app to try uploading it again.</p>



<p>I reached out to their technical support, of course, but thus far have only received mindless responses &#8211; restart your computer, reinstall Backblaze, etc.</p>



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



<p>I received no further response from Backblaze&#8217;s technical support.  They asked me to send them the log files, which I did on January 2nd, 2025, and they never responded again.</p>



<p>As of this update (February 4th 2025) their website still falsely advertises support for files of any size.</p>



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



<p>I was surprised to see that many folks <a href="https://news.ycombinator.com/item?id=42930786" data-wpel-link="external" target="_blank" rel="external noopener">on HackerNews</a> were surprised by the idea of a 1 TiB file.  I certainly agree that&#8217;s large, but it doesn&#8217;t seem unusual or inexplicable to me.  In my case, this particular &#8220;problem&#8221; file is an encrypted, compressed disk image of the boot drive of a prior computer, that I saved when I upgraded to my current computer.</p>



<p>It&#8217;s true that I could <em>probably</em> throw it out at this point &#8211; it was just a precaution in case I forgot to migrate something over, so now (eight years later) it seems that either I made no such mistake or whatever I forgot to migrate doesn&#8217;t matter anyway.  For now I&#8217;ve just manually excluded it from the backup, to work around Backblaze&#8217;s bugs.</p>



<p>There are other cases in which I&#8217;ve had files over 1 TiB, though &#8211; e.g. video files:</p>



<ul class="wp-block-list">
<li>With some cameras and recording modes (e.g. documentarian, interviews) it&#8217;s in principle easy to exceed 1 TiB per file.  e.g. the <a href="https://onlinemanual.nikonimglib.com/z9/en/06_video_recording_02.html#:~:text=Approx.%205780%C2%A0Mbps" data-wpel-link="external" target="_blank" rel="external noopener">Nikon Z9 &amp; Z8 record around 700 MB/s for 8k60 N-RAW</a>, which is about 24 minutes per TiB.<br><br>Note that I don&#8217;t recall if I personally have ever actually exceeded 1 TiB this way.  I mention it mainly for illustration.  It&#8217;s also possible that the Z9 &amp; Z8 shard large recordings into multiple files (I don&#8217;t recall seeing this in years &#8211; not since the 4 GiB per-file limit of cameras a decade ago &#8211; but perhaps I&#8217;ve just not had a single recording large enough).</li>



<li>Usually (for me) it&#8217;s output files that are largest, since they can combine many clips.  I use Final Cut Pro and its video compression capabilities aren&#8217;t great, so I export essentially lossless ProRes and then use ffmpeg or Handbrake for the real compression.  <a href="https://www.apple.com/final-cut-pro/docs/Apple_ProRes.pdf#page=21" data-wpel-link="external" target="_blank" rel="external noopener">ProRes 422 HQ is nearly a gigabyte per second for 8k60</a>, so it takes less than twenty minutes of video to exceed 1 TiB.  Fortunately these large intermediaries only have to live as long as the final compression takes (though that can be days, especially with the latest formats like AV1).</li>
</ul>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/backblaze-seemingly-does-not-support-files-greater-than-1-tb/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2025/01/backblazes-marketing-claims.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8475</post-id>	</item>
		<item>
		<title>presentedWindowStyle is not windowStyle</title>
		<link>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/</link>
					<comments>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 23 Sep 2024 23:12:29 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Feedback Assistant]]></category>
		<category><![CDATA[Happy]]></category>
		<category><![CDATA[presentedWindowStyle]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[windowStyle]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8460</guid>

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



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



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



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



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



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



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



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



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



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



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



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



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



<p>Versus:</p>



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



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



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



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



<p>Lastly, it doesn&#8217;t help that I can&#8217;t find <em>any</em> situation in which <code>presentedWindowStyle(_:)</code> has any effect, even knowing it&#8217;s not intended to style the parent window. One might assume it&#8217;s somehow related to sheets or somesuch, but apparently not? Presumably I&#8217;m overlooking some use-case &#8211; or maybe it <em>doesn&#8217;t</em> actually do anything on macOS, only iDevices? I welcome clues or tips.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/presentedwindowstyle-is-not-windowstyle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8460</post-id>	</item>
		<item>
		<title>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 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="(max-width: 354px) 100vw, 354px" /></figure>
</div>


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


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


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


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


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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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


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


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


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


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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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


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



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



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



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



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



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



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



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



<p>It&#8217;s similarly simple to add a standard Share menu item:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">showSharePopup</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> picker = </span><span style="color: #795E26">NSSharingServicePicker</span><span style="color: #000000">(</span><span style="color: #795E26">items</span><span style="color: #000000">: [</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()])</span></span>
<span class="line"><span style="color: #000000">    picker.</span><span style="color: #795E26">show</span><span style="color: #000000">(</span><span style="color: #795E26">relativeTo</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">bounds</span><span style="color: #000000">, </span><span style="color: #795E26">of</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">, </span><span style="color: #795E26">preferredEdge</span><span style="color: #000000">: .</span><span style="color: #001080">maxY</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> picker = </span><span style="color: #795E26">NSSharingServicePicker</span><span style="color: #000000">(</span><span style="color: #795E26">items</span><span style="color: #000000">: [</span><span style="color: #A31515">&quot;🐞&quot;</span><span style="color: #000000">])</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> pickerMenuItem = picker.</span><span style="color: #001080">standardShareMenuItem</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">action</span><span style="color: #000000"> = </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">showSharePopup</span><span style="color: #000000">(_:))</span></span>
<span class="line"><span style="color: #000000">    pickerMenuItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(pickerMenuItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This one benefits from using the standard, AppKit-provided menu item, so you don&#8217;t need to handle localising its label.</p>



<p>The 🐞 is there for two reasons:</p>



<ol class="wp-block-list">
<li>You have to provide <code><a href="https://developer.apple.com/documentation/appkit/nssharingservicepicker" data-wpel-link="external" target="_blank" rel="external noopener">NSSharingServicePicker</a></code> the nominal item(s) to share up front, in order to initialise it (and so it can tailor its display to the content).  <code><a href="https://developer.apple.com/documentation/appkit/nssharingservicepicker/4031316-standardsharemenuitem" data-wpel-link="external" target="_blank" rel="external noopener">standardShareMenuItem</a></code> should actually be a class member variable, not an instance member variable &#8211; a design flaw in AppKit.<br><br>You don&#8217;t want to invoke the <code>textProvider</code> closure unless you really need to (it could be time-consuming to run, so you don&#8217;t want to run it unnecessarily nor while the user is trying to navigate the contextual menu lest it cause GUI hiccups).  So an equivalent placeholder value &#8211; any other string, in this case &#8211; is better, and suffices.</li>



<li>I use the ladybug emoji so that it stands out if the value ever somehow gets shown to the user (it&#8217;s a bug, get it? 😄).</li>
</ol>



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



<p>This one&#8217;s a little bit more iffy.  If you think you genuinely have need of a Look Up item, consider whether you should be instead making the text selectable and simply utilising the built-in contextual menu support that selectable text views have in SwiftUI.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">lookUp</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">menuItem</span><span style="color: #000000">: NSMenuItem) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">showDefinition</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: </span><span style="color: #795E26">NSAttributedString</span><span style="color: #000000">(</span><span style="color: #795E26">string</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000">()),</span></span>
<span class="line"><span style="color: #000000">                        </span><span style="color: #795E26">at</span><span style="color: #000000">: </span><span style="color: #795E26">NSPoint</span><span style="color: #000000">(</span><span style="color: #795E26">x</span><span style="color: #000000">: CGFloat.</span><span style="color: #001080">infinity</span><span style="color: #000000">, </span><span style="color: #795E26">y</span><span style="color: #000000">: CGFloat.</span><span style="color: #001080">infinity</span><span style="color: #000000">))</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Then, in your `menu(for:)` method:</span></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> lookupItem = </span><span style="color: #795E26">NSMenuItem</span><span style="color: #000000">(</span><span style="color: #795E26">title</span><span style="color: #000000">: submenu == menu ? </span><span style="color: #A31515">&quot;Look Up “</span><span style="color: #0000FF">\(self</span><span style="color: #000000FF">.</span><span style="color: #795E26">textProvider</span><span style="color: #000000FF">()</span><span style="color: #0000FF">)</span><span style="color: #A31515">”&quot;</span><span style="color: #000000"> : </span><span style="color: #A31515">&quot;Look Up&quot;</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                </span><span style="color: #795E26">action</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #0000FF">Self</span><span style="color: #000000">.</span><span style="color: #795E26">lookUp</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                </span><span style="color: #795E26">keyEquivalent</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    lookupItem.</span><span style="color: #001080">target</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span></span>
<span class="line"><span style="color: #000000">    lookupItem.</span><span style="color: #001080">isEnabled</span><span style="color: #000000"> = </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    menu.</span><span style="color: #795E26">addItem</span><span style="color: #000000">(lookupItem)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>This doesn&#8217;t display perfectly.  The use of infinities for the <code>at</code> argument causes it to draw a small yellow box at the top left of the view, layered underneath the contextual menu but usually still visible.  That&#8217;s a hack for cases where you can&#8217;t determine where the text is, or the text being looked up doesn&#8217;t exactly match what&#8217;s shown in the view.</p>



<p>If you know the <em>actual</em> location of the selected text, you can specify that instead to get the yellow text drawn in that location.  But beware: the text from <code>textProvider</code> must <em>exactly</em> match what&#8217;s rendered in the view, otherwise the yellow overlaid box &#8211; intended to look like a highlight effect &#8211; will look weird, because it [re]renders the text based on what <code>textProvider</code> returned.  It also might not correctly match the font, in any case.</p>



<p>Thus why I caution against using the reinvention of this particular wheel.</p>


<ol class="wp-block-footnotes"><li id="0c0ed089-5fc2-434e-8cfb-699487865d79">Possibly borrowed from earlier computers that had multi-mouse-button support, such as some Unixes, but I suspect just coincidence.  I vaguely recall that they were the philosophical antithesis of Apple w.r.t. mouse buttons, with some *nix GUIs <em>requiring</em> at least three mouse buttons for their basic function.  I seem to recall some of them labelling the buttons primary, secondary, and tertiary. <a href="#0c0ed089-5fc2-434e-8cfb-699487865d79-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="2d6f612a-7fb7-498e-bdd6-68f54de88f78">I doubt I came up with this metaphor &#8211; although it&#8217;s pretty obvious in any case &#8211; but it&#8217;s worth considering if it&#8217;s more than just a cute superficial analogy.  AppKit forms the meaningful contents of the sandwich, providing its flavour, substance, and value, while SwiftUI serves only as the bread, there mainly just to convey the contents. 🤔 <a href="#2d6f612a-7fb7-498e-bdd6-68f54de88f78-link" aria-label="Jump to footnote reference 2">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/including-services-in-contextual-menus-in-swiftui/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/03/Good-Services-submenu.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7861</post-id>	</item>
		<item>
		<title>no platform load command found in &#8216;libxyz.a&#8217;, assuming: macOS</title>
		<link>https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/</link>
					<comments>https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 14 Mar 2024 23:36:16 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[LC_BUILD_VERSION]]></category>
		<category><![CDATA[ld]]></category>
		<category><![CDATA[ld_prime]]></category>
		<category><![CDATA[ld-classic]]></category>
		<category><![CDATA[ld64]]></category>
		<category><![CDATA[linker load commands]]></category>
		<category><![CDATA[NASM]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7856</guid>

					<description><![CDATA[This is a linker warning I see frequently since Xcode 15.0. It appears it&#8217;s a result of Apple&#8217;s new linker, &#8220;ld_prime&#8221;, which replaced &#8220;ld64&#8221; that was in use [by Apple] since around 2005 (per Quinn the Eskimo). ☝️ &#8220;ld_prime&#8221; might be an internal code name, or perhaps is just Quinn&#8217;s personal nomenclature. The actual binary&#8230; <a class="read-more-link" href="https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>This is a linker warning I see frequently <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#New-Features" data-wpel-link="external" target="_blank" rel="external noopener">since Xcode 15.0</a>.  It appears it&#8217;s a result of Apple&#8217;s new linker, &#8220;ld_prime&#8221;, which replaced &#8220;ld64&#8221; that was in use [by Apple] since around 2005 (<a href="https://forums.developer.apple.com/forums/thread/715385" data-wpel-link="external" target="_blank" rel="external noopener">per Quinn the Eskimo</a>).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ &#8220;ld_prime&#8221; might be an internal code name, or perhaps is just Quinn&#8217;s personal nomenclature.  The actual binary and project name is simply <code>ld</code> (not to be confused with the <em>original</em> <code>ld</code>, the <em>old</em> old linker, that Apple used until <code>ld64</code> replaced it in 2005).</p>



<p>The old linker &#8211; &#8220;ld64&#8221; &#8211; was retroactively renamed <code>ld-classic</code>.  They emit <em>almost</em> identical version strings, making them easy to confuse at a glance, but for subtly different program and project names:</p>



<pre class="wp-block-preformatted"><strong>👾 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld -v
</strong>@(#)PROGRAM:<strong>ld</strong> PROJECT:<strong>ld</strong>-1053.12
BUILD 15:44:24 Feb  3 2024
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em
will use ld-classic for: armv6 armv7 armv7s arm64_32 i386 armv6m armv7k armv7m armv7em
LTO support using: LLVM version 15.0.0 (static support for 29, runtime is 29)
TAPI support using: Apple TAPI version 15.0.0 (tapi-1500.3.2.2)

<strong>👾 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic -v</strong>
@(#)PROGRAM:<strong>ld-classic</strong>  PROJECT:<strong>ld64</strong>-951.9
BUILD 15:44:42 Feb  3 2024
configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em
LTO support using: LLVM version 15.0.0 (static support for 29, runtime is 29)
TAPI support using: Apple TAPI version 15.0.0 (tapi-1500.3.2.2)</pre>



<p>Tangentially, I believe the similarity of their version output is entirely intentional, as a lot of programs (particularly within build systems like CMake) make assumptions about the output, for better or worse.</p>
</div></div>



<p>Apple&#8217;s new linker appears to be much more pedantic than the old one &#8211; it warns about a lot of things that the old one didn&#8217;t care about.  One of these is missing platform load commands:</p>



<pre class="wp-block-preformatted">/Users/SadPanda/Documents/vmaf/libvmaf/ld:1:1: no platform load command found in 'src/libvmaf.a[62](cpuid.obj)', assuming: macOS</pre>



<p>This doesn&#8217;t technically break anything &#8211; assuming it guessed the platform correctly, which I suspect it just takes as being the host&#8217;s platform &#8211; but it&#8217;s super annoying because it&#8217;s emitted for every afflicted object file the linker sees (that&#8217;s individual files, even if they&#8217;re buried in archive files &#8211; e.g. libfoo.a).  You can have hundreds or even thousands of these warnings for a single library.  Worse, they&#8217;re emitted when you link against the library, not just when you build it.  And with nested static libraries they can propagate up a build chain endlessly.</p>



<p>If we look at the offending file with <code>otool</code>, we see that indeed it has no platform load command:</p>



<pre class="wp-block-preformatted"><strong>👾 otool -vl cpuid.obj</strong>
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 152
  segname 
   vmaddr 0x0000000000000000
   vmsize 0x000000000000002d
  fileoff 208
 filesize 45
  maxprot rwx
 initprot rwx
   nsects 1
    flags (none)
Section
  sectname __text
   segname __TEXT
      addr 0x0000000000000000
      size 0x000000000000002d
    offset 208
     align 2^4 (16)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0
Load command 1
     cmd LC_SYMTAB
 cmdsize 24
  symoff 256
   nsyms 4
  stroff 320
 strsize 56</pre>



<p>For reference, here&#8217;s an example of the load command it&#8217;s looking for:</p>



<pre class="wp-block-preformatted">Load command 1
      cmd LC_BUILD_VERSION
  cmdsize 24
 platform MACOS
    minos 14.2
      sdk 14.4
   ntools 0</pre>



<h2 class="wp-block-heading">Why is it missing?</h2>



<p>There&#8217;s likely many reasons for this, but they all come down to the same general reason:  something is wrong with one [or more] of the tools in the toolchain that created the object file.  All object files <em>should</em> contain this load command, on Apple platforms.  Though the linker currently treats it as optional, the warning is likely a hint from Apple that it may become required in future.  More importantly, without it the linker cannot know which SDK the file is compatible with, and therefore cannot validate that it&#8217;s linking the right things.</p>



<p>In the example case above, the problem tool is <code><a href="https://www.nasm.us" data-wpel-link="external" target="_blank" rel="external noopener">nasm</a></code>.  It&#8217;s an open-source, non-Apple x86-family <a href="https://en.wikipedia.org/wiki/Assembly_language#Assembler" data-wpel-link="external" target="_blank" rel="external noopener">assembler</a>.  It&#8217;s been around for nearly thirty years &#8211; its first platform was DOS.  Though it supports Apple&#8217;s platforms, it is predominately focused on Linux &amp; Windows.  So it&#8217;s not entirely surprising that it&#8217;s causing issues.</p>



<p>It also hasn&#8217;t released even a minor patch update since 2022 (with version 2.16.01), so nominally it predates and doesn&#8217;t support Xcode 15 (announced &amp; released mid-2023).</p>



<p>However, the absence of the <code>LC_BUILD_VERSION</code> command was in fact a problem even before the new Apple linker in Xcode 15, because of cross-compiling (e.g. targeting the iOS simulator on a Mac).  There was even a patch to address the problem, <a href="https://lists.nasm.us/archives/nasm-devel/2021-July/000035.html" data-wpel-link="external" target="_blank" rel="external noopener">submitted by Byoungchan Lee way back in 2021</a>, which was never accepted (it seems to have been completely ignored &#8211; not even a response on the mailing list).</p>



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



<p>No good ones, that I can find.  Stoically ignoring the warning for now is probably the best option.</p>



<p>It is still possible (in Xcode 15, at least) to switch to using the old linker instead, using the <code>-Wl,-ld_classic</code> flags, although that&#8217;s not a long-term solution and may have downsides (e.g. some folks report significantly smaller object files with the new linker).</p>



<p>It&#8217;s <em>probably</em> possible to manually insert the missing load command &#8211; if your build process is amenable to that &#8211; although it&#8217;s not apparent to me what tool or command invocation you would use to do that.  Possibly <code>ld</code> itself, though its documentation provides no insight into this.</p>



<p>For some projects, you might be able to switch to a different assembler (e.g. Apple&#8217;s native <code>as</code> from Clang, or <a href="https://yasm.tortall.net" data-wpel-link="external" target="_blank" rel="external noopener">Yasm</a>, etc).  Apple&#8217;s toolchains don&#8217;t have this problem (so far as I&#8217;ve seen) but are of course not available on other platforms (though, being based on Clang, you can probably get essentially the same thing direct from <a href="https://clang.llvm.org" data-wpel-link="external" target="_blank" rel="external noopener">Clang</a>).  I haven&#8217;t tested whether Yasm has the issue.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/no-platform-load-command-found-in-libxyz-a-assuming-macos/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7856</post-id>	</item>
		<item>
		<title>Copying whole folders in an Xcode Copy Files Build Phase</title>
		<link>https://wadetregaskis.com/copying-whole-folders-in-an-xcode-copy-files-build-phase/</link>
					<comments>https://wadetregaskis.com/copying-whole-folders-in-an-xcode-copy-files-build-phase/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 26 Feb 2024 00:00:26 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Build Phases]]></category>
		<category><![CDATA[Copy Files]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[Xcode]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7783</guid>

					<description><![CDATA[If you try to copy a regular folder (what Xcode calls a &#8220;Group&#8221;) into a file list for any Build Phase, Xcode refuses. But it does work if you use a folder reference . I have been unable to deduce any reason for this.]]></description>
										<content:encoded><![CDATA[
<p>If you try to copy a regular folder <img loading="lazy" decoding="async" width="16" height="13" class="wp-image-7784" style="width: 16px;" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Xcode-group.webp" alt="Xcode Group icon, a grey folder." srcset="https://wadetregaskis.com/wp-content/uploads/2024/02/Xcode-group.webp 16w, https://wadetregaskis.com/wp-content/uploads/2024/02/Xcode-group@2x.webp 32w" sizes="auto, (max-width: 16px) 100vw, 16px" /> (what Xcode calls a &#8220;Group&#8221;) into a file list for any Build Phase, Xcode refuses.  But it <em>does</em> work if you use a folder reference <img loading="lazy" decoding="async" width="16" height="13" class="wp-image-7785" style="width: 16px;" src="https://wadetregaskis.com/wp-content/uploads/2024/02/Xcode-folder-reference.webp" alt="Xcode Folder Reference icon, a blue folder." srcset="https://wadetregaskis.com/wp-content/uploads/2024/02/Xcode-folder-reference.webp 16w, https://wadetregaskis.com/wp-content/uploads/2024/02/Xcode-folder-reference@2x.webp 32w" sizes="auto, (max-width: 16px) 100vw, 16px" />.</p>



<p>I have been unable to deduce any reason for this.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/copying-whole-folders-in-an-xcode-copy-files-build-phase/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7783</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>
		<item>
		<title>NSImage is dangerous</title>
		<link>https://wadetregaskis.com/nsimage-is-dangerous/</link>
					<comments>https://wadetregaskis.com/nsimage-is-dangerous/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 23 Jan 2024 21:53:57 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[AppKit]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[NSBitmapImageRep]]></category>
		<category><![CDATA[NSImage]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[unsafe]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7501</guid>

					<description><![CDATA[NSImage is formally documented as largely not thread-safe: The following classes and functions are generally not thread-safe. In most cases, you can use these classes from any thread as long as you use them from only one thread at a time. Check the class documentation for additional details. Apple&#8217;s Threading Programming Guide &#62; Appendix A:&#8230; <a class="read-more-link" href="https://wadetregaskis.com/nsimage-is-dangerous/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> is <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">formally documented</a> as largely <em>not</em> thread-safe:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The following classes and functions are generally not thread-safe. In most cases, you can use these classes from any thread as long as you use them from only one thread at a time. Check the class documentation for additional details.</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i-CH1-SW1" data-wpel-link="external" target="_blank" rel="external noopener">Threading Programming Guide</a> &gt; <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1" data-wpel-link="external" target="_blank" rel="external noopener">Appendix A: Thread Safety Summary</a>, subsection <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB" data-wpel-link="external" target="_blank" rel="external noopener">Application Kit Framework Thread Safety</a></cite></blockquote>



<p>What &#8220;in most cases&#8221; means is left to the reader&#8217;s imagination.  Apple adds a little addendum for <code>NSImage</code> specifically:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>One thread can create an <code><a href="https://developer.apple.com/documentation/appkit/nsimage" data-wpel-link="external" target="_blank" rel="external noopener">NSImage</a></code> object, draw to the image buffer, and pass it off to the main thread for drawing. The underlying image cache is shared among all threads. For more information about images and how caching works, see <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40003290" data-wpel-link="external" target="_blank" rel="external noopener">Cocoa Drawing Guide</a>. </p>
<cite><a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-126728" data-wpel-link="external" target="_blank" rel="external noopener">NSImage Restrictions</a></cite></blockquote>



<p>For a start, it&#8217;s talking only about <em>creating an NSImage from scratch</em>, not loading it from serialised form (e.g. a file, a pasteboard, etc).  It doesn&#8217;t even deign to mention those other, much more common cases.</p>



<p>And even for that one mentioned use case, what does it mean, exactly?  What &#8220;image cache&#8221; is it referring to?</p>



<p>I don&#8217;t have authoritative answers.  The documentation is so infuriatingly vague that Apple could do basically anything to the implementation, between macOS updates, and claim to have broken no promises.</p>



<p>What I do have is some empirical data and the results of some reverse engineering (shout out to <a href="https://www.hopperapp.com" data-wpel-link="external" target="_blank" rel="external noopener">Hopper</a>), from macOS 14.2 Sonoma.</p>



<h2 class="wp-block-heading">NSImage 101</h2>



<p><code>NSImage</code>s can represent a wide range of imagery.  Most uses of them are probably for bitmap data (i.e. what you find in common image formats like <a href="https://en.wikipedia.org/wiki/WebP" data-wpel-link="external" target="_blank" rel="external noopener">WebP</a> &amp; <a href="https://en.wikipedia.org/wiki/AVIF" data-wpel-link="external" target="_blank" rel="external noopener">AVIF</a>), but <code>NSImage</code> also supports &#8216;raw&#8217; images (e.g. <a href="https://www.nikonusa.com/learn-and-explore/c/products-and-innovation/nikon-electronic-format-nef" data-wpel-link="external" target="_blank" rel="external noopener">Nikon NEF</a>) as well as vector data (e.g. <a href="https://en.wikipedia.org/wiki/SVG" data-wpel-link="external" target="_blank" rel="external noopener">SVG</a> &amp; <a href="https://en.wikipedia.org/wiki/PDF" data-wpel-link="external" target="_blank" rel="external noopener">PDF</a>).  It also has a plug-in mechanism of sorts, so the supported image formats can be extended dynamically at runtime.</p>



<p>You can fetch the full list of supported types from <a href="https://developer.apple.com/documentation/appkit/nsimage/1519988-imagetypes" data-wpel-link="external" target="_blank" rel="external noopener">NSImage.imageTypes</a> &#8211; although it doesn&#8217;t distinguish between those it can read vs those it can write.  Fortunately, <code>sips --formats</code> in Terminal gives you the same list with additional metadata.  On my machine that list happens to be:</p>



<pre class="wp-block-preformatted">Supported Formats:
-------------------------------------------
com.adobe.pdf                pdf   Writable
com.adobe.photoshop-image    psd   Writable
com.adobe.raw-image          dng   
com.apple.atx                --    Writable
com.apple.icns               icns  Writable
com.apple.pict               pict  
com.canon.cr2-raw-image      cr2   
com.canon.cr3-raw-image      cr3   
com.canon.crw-raw-image      crw   
com.canon.tif-raw-image      tif   
com.compuserve.gif           gif   Writable
com.dxo.raw-image            dxo   
com.epson.raw-image          erf   
com.fuji.raw-image           raf   
com.hasselblad.3fr-raw-image 3fr   
com.hasselblad.fff-raw-image fff   
com.ilm.openexr-image        exr   Writable
com.kodak.raw-image          dcr   
com.konicaminolta.raw-image  mrw   
com.leafamerica.raw-image    mos   
com.leica.raw-image          raw   
com.leica.rwl-raw-image      rwl   
com.microsoft.bmp            bmp   Writable
com.microsoft.cur            --    
com.microsoft.dds            dds   Writable
com.microsoft.ico            ico   Writable
com.nikon.nrw-raw-image      nrw   
com.nikon.raw-image          nef   
com.olympus.or-raw-image     orf   
com.olympus.raw-image        orf   
com.olympus.sr-raw-image     orf   
com.panasonic.raw-image      raw   
com.panasonic.rw2-raw-image  rw2   
com.pentax.raw-image         pef   
com.phaseone.raw-image       iiq   
com.samsung.raw-image        srw   
com.sgi.sgi-image            sgi   
com.sony.arw-raw-image       arw   
com.sony.raw-image           srf   
com.sony.sr2-raw-image       sr2   
com.truevision.tga-image     tga   Writable
org.khronos.astc             astc  Writable
org.khronos.ktx              ktx   Writable
org.khronos.ktx2             --    Writable
org.webmproject.webp         webp  
public.avci                  avci  
public.avif                  avif  
public.avis                  --    
public.heic                  heic  Writable
public.heics                 heics Writable
public.heif                  heif  
public.jpeg                  jpeg  Writable
public.jpeg-2000             jp2   Writable
public.jpeg-xl               jxl   
public.mpo-image             mpo   
public.pbm                   pbm   Writable
public.png                   png   Writable
public.pvr                   pvr   Writable
public.radiance              pic   
public.svg-image             svg   
public.tiff                  tiff  Writable</pre>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Sidenote:  notice how it supports modern formats like WebP, AVIF, and JPEG-XL, but <em>only</em> for reading, not writing. 😕</p>



<p>It used to be that <code>NSImage</code> was pretty much a one-stop-shop for typical app image I/O needs, but Apple have for some reason crippled it over the years.  I feel like this is part of a larger trend of Apple providing increasingly less functionality, more complexity, and less coherence in their frameworks.</p>
</div></div>



<p>An <code>NSImage</code> can have multiple &#8216;<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/Images.html#//apple_ref/doc/uid/TP40003290-CH208-SW9" data-wpel-link="external" target="_blank" rel="external noopener">representations</a>&#8216;.  These are essentially just different versions of the image.  One representation might be the original vector form (e.g. an <code><a href="https://developer.apple.com/documentation/appkit/nspdfimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSPDFImageRep</a></code>).  Another might be a rasterisation of that at a certain resolution.  Yet another might be a rasterisation at a different resolution.</p>



<p>It gets more complicated, however, because some representations are merely proxies for <em>other</em> frameworks&#8217; representations, like <a href="https://developer.apple.com/documentation/coregraphics/cgimage/" data-wpel-link="external" target="_blank" rel="external noopener"><code>CGImage</code></a>, <a href="https://developer.apple.com/documentation/coreimage/ciimage/" data-wpel-link="external" target="_blank" rel="external noopener"><code>CIImage</code></a> or somesuch.</p>



<p>Nonetheless, for the simple case of a static bitmap image read from a file, generally <code>NSImage</code> produces just one representation, which is the full bitmap (as an <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code>).  That&#8217;s the only case I&#8217;m going to discuss here, for simplicity&#8217;s sake (though likely the lessons apply to the other cases too).</p>



<h2 class="wp-block-heading">Accessing / using an NSImage</h2>



<p>Generally to use an <code>NSImage</code> you need a bitmap representation.  e.g. to actually draw it to the screen.</p>



<p>Unless you manually create an <code>NSImage</code> from an in-memory bitmap, the bitmap representation is not loaded initially, but rather only when first needed.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>☝️ You can usually obtain the <code>NSBitmapImageRep</code> (via e.g. <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519961-bestrepresentation" data-wpel-link="external" target="_blank" rel="external noopener">bestRepresentation(for: .infinity, context: nil, hints: nil)</a></code>) even before it&#8217;s actually been loaded &#8211; initially it&#8217;s largely just a shell, that knows its metadata (e.g. dimensions and colour space) but not yet its actual imagery.</p>
</div></div>



<p>Ultimately the load is triggered when something asks for the raw bytes of the bitmap (either you, directly in your code, or indirectly when you e.g. ask AppKit to draw the image).</p>



<p>Fetching the raw bytes of a bitmap from <code>NSBitmapImageRep</code> is not a trivial exercise.  I&#8217;m going to gloss over the complexities (like planar data formats) and just talk about the common case of single-plane (&#8220;interleaved channels&#8221;) bitmaps.</p>



<p>For those, you can access the raw bytes via the <a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395421-bitmapdata" data-wpel-link="external" target="_blank" rel="external noopener"><code>bitmapData</code></a> property.</p>



<p>Nominally, <code>bitmapData</code> just returns a <code>uint8_t*</code>.</p>



<p>In fact, <code>bitmapData</code> calls a <em>lot</em> of internal methods in a complicated fashion, with many possible code paths.  What exactly it does depends on the underlying source of data, but in a nutshell it checks if the desired data has already been created / loaded (it&#8217;s cached in an instance variable) and if not it loads it, e.g. by calling <code><a href="https://developer.apple.com/documentation/imageio/1465011-cgimagesourcecreateimageatindex" data-wpel-link="external" target="_blank" rel="external noopener">CGImageSourceCreateImageAtIndex</a></code>.</p>



<p>That&#8217;s what makes <code>NSImage</code> dangerous to use from multiple threads simultaneously.</p>



<figure class="wp-block-pullquote"><blockquote><p>There is no mutual exclusion protecting any of these code paths.</p></blockquote></figure>



<p>Reading the <em>cached</em> bitmap data is essentially read-only (just some retain/autorelease traffic) so it&#8217;s safe to call from multiple threads concurrently (as long as something ensures the <code>NSBitmapImageRep</code> is kept alive the whole time <em>and not mutated</em>)</p>



<p>But loading or modifying it is not.  As such, if you call <code>bitmapData</code> from multiple threads concurrently, and you don&#8217;t know for sure that it&#8217;s already fully loaded, you get a <a href="https://www.avanderlee.com/swift/race-condition-vs-data-race/" data-wpel-link="external" target="_blank" rel="external noopener">data race</a> (also known as a &#8220;WTF does my app crash randomly?!&#8221; condition).</p>



<p>The consequences of that race vary.  Maybe you &#8220;win&#8221; the race &#8211; one thread happens to run virtually to completion of <code>bitmapData</code> first, storing the fresh backing data into the caching instance member, and then all the other threads run and just return that same value &#8211; the ideal situation as everything works as intended.</p>



<p>Maybe you &#8220;lose&#8221; the race: every concurrent thread checks simultaneously and sees there&#8217;s no cached value, so they all &#8211; in parallel and redundant to each other &#8211; load the bitmap data and store it into the cache.  They each return the one they created, even though ultimately only one thread wins &#8211; the <em>last</em> one to write into the cache &#8211; and the duplicate bitmap data that all the earlier threads created is deallocated.  Even though pointers to them have been returned to you.  And you might be in the middle of using them.  Causing you to crash with a memory protection fault (or worse, read from some random other memory allocation that happened to be placed at the same address afterwards, reading essentially garbage).</p>



<p>Hypocritically, this is because Apple don&#8217;t follow <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters#Pass-a-Constant-Pointer-as-a-Parameter" data-wpel-link="external" target="_blank" rel="external noopener">their own rules on escaping pointers</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The pointer you pass to the function is only guaranteed to be valid for the duration of the function call. Do not persist the pointer and access it after the function has returned.</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/documentation/swift/swift-standard-library" data-wpel-link="external" target="_blank" rel="external noopener">Swift Standard Library</a> &gt; <a href="https://developer.apple.com/documentation/swift/manual-memory-management" data-wpel-link="external" target="_blank" rel="external noopener">Manual Memory Management</a> &gt; <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters" data-wpel-link="external" target="_blank" rel="external noopener">Calling Functions With Pointer Parameters</a>, subsection <a href="https://developer.apple.com/documentation/swift/calling-functions-with-pointer-parameters#Pass-a-Constant-Pointer-as-a-Parameter" data-wpel-link="external" target="_blank" rel="external noopener">Pass a Constant Pointer as a Parameter</a>.</cite></blockquote>



<p>The source code for the relevant <code>NSBitmapImageRep</code> methods is 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:4;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: #0000FF">uint8_t</span><span style="color: #000000">*)bitmapData {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">uint8_t</span><span style="color: #000000"> **result = </span><span style="color: #0000FF">nil</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">getBitmapDataPlanes:</span><span style="color: #000000">&amp;result];</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> *result;</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">void</span><span style="color: #000000">)getBitmapDataPlanes:(</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">***)output {</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">_performBlockUsingBackingMutableData:</span><span style="color: #000000">^</span><span style="color: #795E26">void</span><span style="color: #000000"> (</span><span style="color: #0000FF">uint8_t</span><span style="color: #000000">* </span><span style="color: #001080">dataPlanes</span><span style="color: #000000">[</span><span style="color: #098658">5</span><span style="color: #000000">]) {</span></span>
<span class="line"><span style="color: #000000">        *output = dataPlanes;</span></span>
<span class="line"><span style="color: #000000">    }];</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="has-text-align-center has-x-large-font-size">🤦‍♂️</p>



<figure class="wp-block-pullquote"><blockquote><p>The <code>bitmapData</code> property and <code>getBitmapDataPlanes</code> methods are fundamentally unsafe.</p></blockquote></figure>



<p>Unfortunately, while <code>_performBlockUsingBackingMutableData</code> and its many similar siblings <em>can</em> be made safe, they are (a) all private and (b) not currently enforcing the necessary mutual exclusion.  This could be corrected by Apple in future (although I wouldn&#8217;t hold your breath).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p>Nominally the <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395583-colorat" data-wpel-link="external" target="_blank" rel="external noopener">colorAt(x:y:)</a></code> method could also be safe, but it currently lacks the same underlying mutual exclusion &#8211; and even if it didn&#8217;t, the performance when using it is atrocious due to the Objective-C method call overhead.  Even utilising <a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeSpeed/Articles/CriticalCode.html#//apple_ref/doc/uid/20001871-98344" data-wpel-link="external" target="_blank" rel="external noopener">IMP caching</a>, the performance is still terrible compared to directly accessing the bitmap byte buffer.</p>
</div></div>



<h2 class="wp-block-heading">Why does it matter that NSImage is not thread-safe?</h2>



<p>The crux of the problem is that you often have no good choice about it, because <code>NSImage</code> is a <a href="https://forums.swift.org/t/what-does-currency-type-mean/41065" data-wpel-link="external" target="_blank" rel="external noopener">currency type</a> used widely throughout Apple&#8217;s own frameworks, and you often have no control over what thread it&#8217;s created or used on.</p>



<p>For example, even using the very latest Apple APIs such as SwiftUI&#8217;s <code><a href="https://developer.apple.com/documentation/swiftui/view/dropdestination(for:action:istargeted:)" data-wpel-link="external" target="_blank" rel="external noopener">dropDestination(for:action:isTargeted:)</a></code>, you cannot control which thread the <code>NSImage</code>s are created on (it <em>appears</em> to be the main thread, although that API provides no guarantees).</p>



<p>Similarly you have no control over where those images are used if you pass them to 3rd party code &#8211; including Apple&#8217;s.  e.g. <code><a href="https://developer.apple.com/documentation/swiftui/image/init(nsimage:)" data-wpel-link="external" target="_blank" rel="external noopener">Image(nsImage:)</a></code>; <em>possibly</em> that only uses them on the main thread, but it <em>might</em> be pre-rendered the image a separate thread for better performance (to avoid blocking the main thread and causing the app to hang).  In fact it <em>should</em>, in principle.</p>



<p>Loading an image &#8211; actually reading it from a file or URL and decompressing it into a raw bitmap suitable for drawing to the screen &#8211; should never be done on the main thread, because it can take a long time.  A 1 GiB TIFF takes nearly 30 seconds on my iMac Pro, for example (and TIFF uses very lightweight compression, so it&#8217;s a relatively fast-to-read format).  Anything involving the network could take an unbounded amount of time.  Even small files &#8211; like a 20 MiB NEF &#8211; can take seconds to render because they are non-trivial to decode and/or decompress.</p>



<p>So you&#8217;re screwed on multiple levels:  not only can you generally not guarantee what thread <code>NSImage</code> is born on nor used on, you <em>can&#8217;t</em> use it exclusively on the main thread because that will cause a terrible user experience.</p>



<h2 class="wp-block-heading">What can you do?</h2>



<p>In simple terms, the best you can realistically do is try ensure that all modifications to the <code>NSImage</code> (including implicit ones, such as loading a bitmap representation upon first use) happen exclusively in one thread [at a time].  For example, in my experiments, putting a lock around otherwise concurrent calls to <code>bitmapData</code> is enough to prevent any data races.  Although I am mystified as to how I can still let the main thread draw the image concurrently without any apparent problems. 🤔</p>



<p>If you want to play it <em>really</em> safe, you have to create &amp; pre-load each <code>NSImage</code> on a single thread (<em>not</em> the main thread), before ever sending it to another isolation domain.  That means manually reimplementing things like drag and drop of images (because you can no longer work directly with <code>NSImage</code> with any drag &amp; drop APIs, you have to instead use only &#8211; and <em>all</em> &#8211; the things that <em>could potentially be</em> images, like URLs or data blobs, and then translate those to &amp; from <code>NSImage</code>s manually).</p>



<p>Complicating all this is that <code>NSImage</code> is unclear about how <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/Images.html#//apple_ref/doc/uid/TP40003290-CH208-SW11" data-wpel-link="external" target="_blank" rel="external noopener">its caching</a> works.  For starters, does this caching apply to the representations or is it a hidden, orthogonal system?  Is it thread-safe?  Etc.</p>



<p>You can supposedly modify the caching behaviour via the <code><a href="https://developer.apple.com/documentation/appkit/nsimage/1519850-cachemode" data-wpel-link="external" target="_blank" rel="external noopener">cacheMode</a></code> property, but in my experience there is no apparent effect no matter what it is set it to (not on the image&#8217;s representations nor on render performance in the GUI).</p>



<p>It&#8217;s a shame that <code>NSImage</code> has been so neglected, and has so many glaring problems.  Over the years Apple have seemingly tried to replace it, introducing new image types <a href="https://developer.apple.com/documentation/coreimage/ciimage/" data-wpel-link="external" target="_blank" rel="external noopener">over</a> and <a href="https://developer.apple.com/documentation/uikit/uiimage/" data-wpel-link="external" target="_blank" rel="external noopener">over</a> again, but all that&#8217;s done is <a href="https://xkcd.com/927/" data-wpel-link="external" target="_blank" rel="external noopener">made everything more complicated</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nsimage-is-dangerous/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/01/NSImage-thread-unsafety-caught-by-Address-Sanitizer-MallocScribble.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">7501</post-id>	</item>
		<item>
		<title>Reminder: macOS system frameworks binaries are hidden (since Big Sur)</title>
		<link>https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/</link>
					<comments>https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 23 Jan 2024 18:59:12 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[dyld]]></category>
		<category><![CDATA[dyld-shared-cache-extractor]]></category>
		<category><![CDATA[Hopper]]></category>
		<category><![CDATA[macOS]]></category>
		<category><![CDATA[macOS Big Sur]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=7503</guid>

					<description><![CDATA[Every now and again I&#8217;ll go to do something really innocuous with an Apple framework, like disassemble it in Hopper or check the link headers. And every. single. time. I forget that Apple did some really weird shit in Big Sur, and removed the binaries. $ ls -lh /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit ls: /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit: No such file or&#8230; <a class="read-more-link" href="https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Every now and again I&#8217;ll go to do something really innocuous with an Apple framework, like disassemble it in <a href="https://www.hopperapp.com" data-wpel-link="external" target="_blank" rel="external noopener">Hopper</a> or check the link headers.  And <em>every. single. time</em>. I forget that Apple did some really weird shit in Big Sur, and removed the binaries.</p>



<pre class="wp-block-preformatted">$ ls -lh /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit
ls: /System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit: No such file or directory</pre>



<p>WTF, mate?</p>



<p>Invariably I spend half an hour websearching around to try to figure out how the hell my system got so broken, and how it&#8217;s possible to even boot macOS in such a corrupt state, until <em>finally</em> I chance upon <a href="https://mjtsai.com/blog/2020/06/26/reverse-engineering-macos-11-0/" data-wpel-link="external" target="_blank" rel="external noopener">Michael Tsai&#8217;s excellent summary of how Apple broke their frameworks starting in Big Sur</a>.</p>



<p>The good news for Hopper is that it has since been updated to work around this &#8211; you can access the Apple framework binaries through <em>File</em> > <em>Read File from DYLD Cache…</em>  There&#8217;s also tools like <a href="https://github.com/keith/dyld-shared-cache-extractor" data-wpel-link="external" target="_blank" rel="external noopener">dyld-shared-cache-extractor</a> which can resurrect the binaries from the cache.</p>



<p>Note also that in Sonoma, at least, the cache lives at <code>/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/</code> (in previous macOS releases it was apparently in <code>/System/Library/dyld/</code>).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/reminder-macos-system-frameworks-binaries-are-hidden-since-big-sur/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7503</post-id>	</item>
		<item>
		<title>SwiftData pitfalls</title>
		<link>https://wadetregaskis.com/swiftdata-pitfalls/</link>
					<comments>https://wadetregaskis.com/swiftdata-pitfalls/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 15 Nov 2023 21:04:36 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Core Data]]></category>
		<category><![CDATA[MySQL Workbench]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftData]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=5378</guid>

					<description><![CDATA[I&#8217;ve been exploring SwiftData lately, and I&#8217;ve been unpleasantly surprised by how many sharp edges it has. I&#8217;m going to try to describe some of them here, so that hopefully others can avoid them (or perhaps be dissuaded from using SwiftData to begin with). I&#8217;m using Xcode 15.0.1 (Swift 5.9) on macOS 14.1 (Sonoma). Background&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swiftdata-pitfalls/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;ve been exploring SwiftData lately, and I&#8217;ve been unpleasantly surprised by how many sharp edges it has.  I&#8217;m going to try to describe some of them here, so that hopefully others can avoid them (or perhaps be dissuaded from using SwiftData to begin with).</p>



<p>I&#8217;m using Xcode 15.0.1 (Swift 5.9) on macOS 14.1 (Sonoma).</p>



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



<p>I&#8217;ve dabbled in Core Data over the years &#8211; mostly pre-Swift &#8211; and have a kind of begrudging, distant respect.  I understand what it&#8217;s trying to do and I can appreciate some of the ways in which it makes that easier, but I&#8217;m also all too familiar with its limitations and caveats.  So mostly I&#8217;ve ignored it, preferring to use other approaches &#8211; whether that be JSON or plists for simple cases, or just directly using a SQL database when that feature-set genuinely is warranted.</p>



<p>When SwiftData was introduced, I was intrigued.  On the surface it seemed like it greatly reduced the boilerplate and some of the complexity of using Core Data.  It at least seemed like an appealing option for light use, with relatively simple models and especially with SwiftUI.  It seems pretty obviously targeted at the typical, pretty trivial patterns that most iOS apps have &#8211; i.e. not much more than lists of objects.</p>



<p>But it&#8217;s only recently that I actually tried using SwiftData for more than just some trivial toy cases.  I had a textbook case of a hierarchical type tree, with fairly simple 1-to-1 or many-to-1 relationships, the need for cascade deletes in some cases but not others, and a dataset a little larger than what I could get away with just stuffing into User Defaults.</p>



<p>Alas, what I&#8217;ve found is that SwiftData has so many glaring bugs and limitations, that even for this textbook example case, it just doesn&#8217;t make any sense to use it.</p>



<h2 class="wp-block-heading">Example use case</h2>



<p>My model is fairly simple &#8211; a root object called <code>House</code> which can contain multiple <code>Floor</code>s each of which has multiple <code>Room</code>s.  Each of those has a few other attributes &#8211; simple strings and numbers &#8211; and the <code>Room</code>s can reference a few other model types, like <code>Weapon</code>s and <code>Monster</code>s, as 1-to-1 relationships.</p>



<p>The order of floors and rooms matters &#8211; they correspond to top to bottom (attic to basement) and left to right as will be rendered in the GUI.</p>



<h2 class="wp-block-heading">Design flaws</h2>



<h3 class="wp-block-heading" id="autosave-does-not-work">Auto-save doesn&#8217;t work</h3>



<p>By default, model containers are <em>supposed</em> to auto-save.  That&#8217;s what the documentation says, both in <a href="https://developer.apple.com/documentation/swiftui/view/modelcontainer(for:inmemory:isautosaveenabled:isundoenabled:onsetup:)-8oc48" data-wpel-link="external" target="_blank" rel="external noopener">the API reference</a> and the tutorials:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>In both instances, the returned context periodically checks whether it contains unsaved changes, and if so, implicitly saves those changes on your behalf. For contexts you create manually, set the&nbsp;<a class="" href="https://developer.apple.com/documentation/swiftdata/modelcontext/autosaveenabled"><code>autosaveEnabled</code></a>&nbsp;property to&nbsp;<code>true</code>&nbsp;to get the same behavior [sic].</p>
<cite>Apple&#8217;s <a href="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches#Save-models-for-later-use" data-type="link" data-id="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches#Save-models-for-later-use" data-wpel-link="external" target="_blank" rel="external noopener">Save models for later use</a> (subsection of <a href="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches" data-wpel-link="external" target="_blank" rel="external noopener">Preserving your app’s model data across launches</a>)</cite></blockquote>



<p>I suppose it hinges on the word &#8220;periodically&#8221;.  In my testing, that period must be tens of seconds, or worse.  It&#8217;s trivial to see that it doesn&#8217;t work &#8211; just make some changes and close the window, or quit your app.  Virtually all the time &#8211; even if you wait several seconds &#8211; those changes are silently lost!</p>



<p>It&#8217;s clear that SwiftData has no hooks into deinitialisation, view / window closure, app exit, etc.  You would think it <em>has</em> to, in order to save things reliably even in the happy-path cases (as opposed to e.g. app crashes).  But it simply does not, and thus it does not.</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>If you want full resilience to unexpected app termination (e.g. crashes) then <em>any</em> time you make a change to <em>any</em> SwiftData-managed object, you have to immediately call <code>context.save()</code> manually.  Which is especially annoying since that method throws, so you have to figure out some sane way to handle that failure possibility in every situation in which you make any changes to your models.</p>



<p>For non-trivial applications, this leads to your code being <em>riddled</em> with save-management code.  Think about every SwiftUI field which reflects an element of your model and enables mutation of it.  Every button that adds or removes or reorders anything.  Every editable list view.  Etc.</p>



<p>It&#8217;s easy to miss a spot where you make a modification, leading to silent data loss.</p>



<p>If you&#8217;re willing to lose data when your app crashes (which you really <em>shouldn&#8217;t</em> be willing to do so easily do), you can limit your saves to certain key paths &#8211; e.g. window closure, app exit, etc.  But you have to find and hook into all those things manually.</p>



<p>This is nominally no different to some SwiftData alternatives (e.g. <code>NSCoding</code>), although below par compared to User Defaults and most other SQL database frameworks.</p>



<h3 class="wp-block-heading">Arrays of <code>@Model</code> objects are randomly shuffled</h3>



<p><code>Array</code> in Swift is very explicitly an <em>ordered</em> collection, in the sense that the elements within have a specific albeit arbitrary order, based on how they&#8217;re inserted.  This is the same as arrays / vectors / lists in practically every programming language.</p>



<p>SwiftData violates this by randomly reordering elements at various times, such as when [re]loading model objects from persist storage.</p>



<p>The reason is pretty simple &#8211; it fails to record the order of elements in its underlying SQLite database, instead using a randomly-assigned, arbitrary integer as a uniquing key, and nothing else.</p>



<p>This is a pretty startling design flaw.  I&#8217;m not sure how SwiftData got out the door with this.</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>I haven&#8217;t found any good workarounds.  The advice you&#8217;ll generally see online (e.g. <a href="https://stackoverflow.com/questions/76889986/swiftdata-ios-17-array-in-random-order" data-wpel-link="external" target="_blank" rel="external noopener">on StackOverflow</a>) is to manually load the objects in the array, specifying a sort order as part of the query.  But this basically has you manually reimplementing the relational aspects of SwiftData:</p>



<ul class="wp-block-list">
<li>You have to add a member variable to the target class in question <em>just</em> to store its index in the array.  You need one of these variables for <em>every</em> many-to relationship it can be a part of.</li>



<li>You have to then somehow populate those variables <em>and</em> keep them in sync with any changes to the arrays.</li>



<li>You can&#8217;t actually use the array member variables &#8211; since the objects are in random order &#8211; and instead have to manually query the database separately.  Or, you can sort the array member variables at some point, but it&#8217;s difficult to do that efficiently (you can&#8217;t really be sure when SwiftData is going to mangle the sort order, so you basically have to do the sorting on the fly every single time you access the array).</li>
</ul>



<p>Rather than going through all that hassle, it&#8217;s easier to just not use SwiftData &#8211; e.g. you can simply use a normal object graph that&#8217;s persisted to disk as JSON, or in property list form, or using <code>NSCoding</code>, etc.</p>



<h3 class="wp-block-heading"><code>let</code> cannot be used for bidirectional relationships</h3>



<p>All member variables (of <code>@Model</code> classes) that refer bidirectionally to other model classes <em>must</em> be variables (<code>var</code>), not constants (<code>let</code>).  Irrespective of their actual intended semantics.</p>



<p>This is seemingly just an inherent part of SwiftData (and Core Data underneath) &#8211; the presumption that <em>all</em> relationships are optional <em>and</em> can change arbitrarily.  It worked okay in Objective-C because that was closer to the reality of Objective-C objects, but it flies in the face of Swift&#8217;s much stronger controls and expectations on mutability and optionality.</p>



<p>It would be a relatively minor annoyance &#8211; losing compile-time protection of immutability is frustrating but not technically a show-stopper &#8211; except that there are <em>no</em> warnings or indications otherwise, of any kind, that you&#8217;re &#8220;holding it wrong&#8221;.  If you declare the member with <code>let</code>, everything will compile without so much as a warning, but you&#8217;ll run into obtuse runtime errors and crashes, e.g.:</p>



<pre class="wp-block-preformatted">💣 Could not cast value of type 'Swift.KeyPath&lt;Game.House, Swift.Array&lt;Game.Floor&gt;&gt;' (0x12e00cc98) to 'Swift.ReferenceWritableKeyPath&lt;Game.House, Swift.Array&lt;Floor.Region&gt;&gt;' (0x13e0b4198).</pre>



<p>What that error message is <em>trying</em> but failing to convey is that you cannot (outside of initialisation) write to a <code>let</code>, of course, so its attempt to modify the property failed.  So it crashed your app.</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>None, really.  You just have to remember, <em>every time</em>, to avoid <code>let</code>.  Possibly you could obtain &amp; configure some linter to check this for you.</p>



<h3 class="wp-block-heading" id="relationships-are-always-optional">Relationships are secretly implicitly unwrapped by default</h3>



<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">@Model</span></span>
<span class="line"><span style="color: #0000FF">final</span><span style="color: #000000"> </span><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">Room</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"> monster: Monster</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    …</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>That&#8217;s fine, right &#8211; every room has a <code>Monster</code>.  Simple.</p>



<p>Except it&#8217;s not, if <code>Monster</code> is a <code>@Model</code>.  In that case, despite the fact you&#8217;ve declared it as non-optional, it actually is optional.  <code>@Model</code> essentially makes it implicitly unwrapped without telling you (and without the important <code>!</code> character after the type to warn you).</p>



<p>The technical reason it&#8217;s implicitly and unavoidably optional is because <code>Room</code> and <code>Monster</code> are stored in separate SQLite tables, with a nullable foreign key relationship between them.  SwiftData does not support non-nullable foreign keys.  So it&#8217;s totally valid, as far as the actual database layer and persistent storage are concerned, for the relevant <code>Monster</code> to disappear entirely.  But that means your model instance is basically corrupt when you load it, which can have a variety of ill-defined effects such as crashing your app.</p>



<p>You might think this is largely hypothetical, or academic &#8211; SwiftData&#8217;s not going to erroneously delete your <code>Monster</code> out from under the <code>Room</code> (you assume), and you&#8217;re not going to do it yourself, so it&#8217;ll never happen, right?  Well, maybe.  Consider what happens as your app evolves, undergoes refactors, etc.  It&#8217;s quite possible you unwittingly create a situation in which what <em>was</em> previously valid data is no longer valid to your app.</p>



<p>Or, you might run into a SwiftData design flaw or bug which causes the data to be corrupted on save, such as is detailed in the next section.  Even if the root cause is that your data was corrupted at save time, it&#8217;s frustrating to have that manifest only through obtuse crashes at <em>some ill-defined time</em> in the future after your app is relaunched.</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>None, really.</p>



<p>At the very least, you can explicitly make <em>all</em> relationships optional.  That way you can code defensively around them, by putting in appropriate unwrapping code and guards.  If nothing else you can at the very least crash with a clear failure message (there is no log message at all when SwiftData crashes your app via implicit unwrapping).</p>



<h3 class="wp-block-heading" id="you-cannot-directly-initialise-bidirectional-relationship-properties">You cannot directly initialise bidirectional relationship properties</h3>



<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">@Model</span></span>
<span class="line"><span style="color: #0000FF">final</span><span style="color: #000000"> </span><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">House</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@Relationship</span><span style="color: #000000">(deleteRule: .</span><span style="color: #001080">cascade</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                  inverse: \Floor.</span><span style="color: #001080">house</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"> floors: [Floor]</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">floors</span><span style="color: #000000">: [Floor]) {</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">floors</span><span style="color: #000000"> = floors</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>If you do something like the above &#8211; making use of Xcode to generate your initialiser for you, like you normally would for classes &#8211; you&#8217;ll get the wonderful behaviour that everything works fine until you quit your app and reload it.  Then you&#8217;ll discover that <code>floors</code> is empty.</p>



<p>There are no error messages, neither at compile time nor runtime.  No warnings.  <em>Nothing</em>.</p>



<p>Looking at the underlying SQLite database, its apparent that <em>something</em> is seriously broken because while it <em>does</em> save the <code>Floor</code> instances, their foreign key back to <code>House</code> is <code>NULL</code> (even if logically that should be impossible &#8211; refer back to the prior point on how <a href="#relationships-are-always-optional" data-type="internal" data-id="#relationships-are-always-optional">SwiftData forces <em>all</em> relationships to be optional</a>).</p>



<p>What&#8217;s happening is that the initialisation of <code>floors</code> in <code>init</code> is in some sense bypassing SwiftData&#8217;s custom hooks; it&#8217;s &#8220;directly&#8221; setting the value in memory without SwiftData updating its corresponding Core Data model underneath (this is another place SwiftData&#8217;s abstraction is leaky &#8211; your <code>@Model</code> classes aren&#8217;t the <em>real</em> model classes, they&#8217;re merely a layer atop the <em>actual</em> Core Data models).</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>Thankfully this <em>does</em> have a simple workaround (iff you know it and can remember to always use it):  never <em>assign</em> the relationship in <code>init</code>.  You can <em>append</em> to it, and you can assign it <em>outside</em> of <code>init</code>.  So 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">@Model</span></span>
<span class="line"><span style="color: #0000FF">final</span><span style="color: #000000"> </span><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">House</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@Relationship</span><span style="color: #000000">(deleteRule: .</span><span style="color: #001080">cascade</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                  inverse: \Floor.</span><span style="color: #001080">house</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"> floors: [Floor] = []</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">floors</span><span style="color: #000000">: [Floor]) {</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">floors</span><span style="color: #000000">.</span><span style="color: #795E26">append</span><span style="color: #000000">(</span><span style="color: #795E26">contentsOf</span><span style="color: #000000">: floors)</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">Relationship constraints are only enforced at <em>save</em> time</h3>



<p><code>@Relationship</code> lets you specify the arity of a relationship &#8211; the minimum and maximum number of related objects that are permitted.  However, this is not actually enforced at any time <em>except</em> save time (when you <em>explicitly</em> call <code>context.save()</code>).</p>



<p>Arguably this is better than nothing &#8211; vanilla <code>Array</code> member variables provide no way to constrain the array&#8217;s contents &#8211; but it can lead to mistakes if you unwittingly rely on more rigorous enforcement.</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>None, really, that I&#8217;ve been able to come up with.</p>



<p>It sort of helps to think of these &#8216;constraints&#8217; as actually just &#8216;guidelines&#8217;; to not <em>actually</em> rely on them being enforced.  That may mean you have to do manual validation, that&#8217;s in principle redundant, in order to ensure the rules are enforced.</p>



<p>That said, since you need to manually save aggressively anyway (since <a href="#autosave-does-not-work">auto-save doesn&#8217;t work</a>), you might be able to conjoin the two workarounds.</p>



<h3 class="wp-block-heading">Singletons are unsupported</h3>



<p>In some use-cases your root object is a singleton &#8211; you don&#8217;t ever actually want to have multiple instances of it, it&#8217;s merely the top-level of your object graph, the object which organises all other objects.</p>



<p>Unfortunately SwiftData doesn&#8217;t support this.  Every <code>@Model</code> class can have multiple instances.</p>



<p>This can make things pretty awkward when dealing with your singleton, as you cannot do intuitive and simple 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: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Map</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@Environment</span><span style="color: #000000">(\.</span><span style="color: #001080">modelContext</span><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"> context</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@Query</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> house: House</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">        VStack {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">ForEach</span><span style="color: #000000">(house.</span><span style="color: #001080">floors</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>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h4 class="wp-block-heading">Workarounds</h4>



<p>Basically you have three choices:</p>



<ul class="wp-block-list">
<li>Remove all your singletons, redesigning your model and app to accomodate.  This <em>might</em> make some contrived sense if you rationalise it as a way to have multiple game sessions concurrently, or to facilitate concurrent unit testing, or somesuch.  But you&#8217;ll know in your heart that it&#8217;s not real.</li>



<li>Use force-unwrapping (or equivalent), making your app crash or otherwise fail if your singleton expectations are violated by SwiftData.</li>



<li>Silently ignore all but an arbitrary instance of the model class in question.</li>
</ul>



<p>These are all pretty flawed.  And no matter which approach you choose, they introduce some significant boilerplate into your 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">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Map</span><span style="color: #000000">: View {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@Environment</span><span style="color: #000000">(\.</span><span style="color: #001080">modelContext</span><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"> context</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@Query</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> houses: [House]</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">        VStack {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">ForEach</span><span style="color: #000000">(house[</span><span style="color: #098658">0</span><span style="color: #000000">].</span><span style="color: #001080">floors</span><span style="color: #000000">) { </span><span style="color: #008000">// Silently ignore other houses.</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>



<p>In the above example, which house is chosen is arbitrary and may change between view updates.  You <em>can</em> do extra work to pick a house deterministically &#8211; e.g. sort by some unique attribute and pick the first &#8211; and you probably <em>should</em> if you&#8217;re going to use this hack, but you&#8217;ll still be operating in a messed-up state.</p>



<h3 class="wp-block-heading">You cannot create model objects outside of SwiftData contexts</h3>



<p>This limitation is explicable and reasonable on the face of it, but does limit your app architecture in ways that can be annoying.  e.g. you cannot initialise your views with test models like you normally would:</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">#Preview {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">MapView</span><span style="color: #000000">(</span><span style="color: #795E26">house</span><span style="color: #000000">: House.</span><span style="color: #795E26">default</span><span style="color: #000000">())</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>That will compile without any complaints, but when you try to actually view the preview in Xcode you&#8217;ll get the dreaded &#8220;Cannot preview in this file&#8221; error, with absolutely no indication why.</p>



<p><em>If</em> you happen to try a similar thing in your app initialisation, 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">@main</span></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Game</span><span style="color: #000000">: App {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some Scene {</span></span>
<span class="line"><span style="color: #000000">        WindowGroup {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">Overview</span><span style="color: #000000">(</span><span style="color: #795E26">house</span><span style="color: #000000">: House.</span><span style="color: #795E26">default</span><span style="color: #000000">())</span></span>
<span class="line"><span style="color: #000000">                .</span><span style="color: #795E26">modelContainer</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: [House.self])</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…then you can at least run your app and will see an error message before it crashes, like:</p>



<pre class="wp-block-preformatted">💣 SwiftData/ModelContainer.swift:144: Fatal error: failed to find a currently active container for Room
Failed to find any currently loaded container for Room) [sic]</pre>



<p>So far as Apple error messages go, this is practically perfect &#8211; it at least does mention <em>almost</em> the pertinent object (the [SwiftData] container is <em>similar</em> to the SwiftData context) and provide some explanation of the genuine problem.</p>



<h4 class="wp-block-heading">Workarounds</h4>



<p>I haven&#8217;t explored this in great detail, so there might be other options, but the best I could promptly come up with is to move initialisation inside your SwiftUI views.  Whether that be through explicit user interaction (e.g. a &#8220;New Game&#8221; GUI in my use case, when there&#8217;s no existing <code>House</code> in the database), or in a suitable <code>onAppear</code> handler, etc.</p>



<p>That&#8217;s reasonable for actual app execution, but doesn&#8217;t immediately help you for SwiftUI previews.  However, if you attach a suitable model container to your preview instance:</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">#Preview {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">MapView</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        .</span><span style="color: #795E26">modelContainer</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: [House.self])</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…then your preview will actually use the real app data.  This can actually be handy sometimes, as you can manipulate the state &#8211; either in the preview or your actual app execution &#8211; which can be handy for debugging view problems (e.g. you&#8217;re running your app, playing around, and you notice a rendering error &#8211; you can quit your app and its saved state will be used in your Xcode previews, automatically showing you the view in its broken state).</p>



<p>Alternatively, you can go through a more laborious process of [more-]manually creating the SwiftData context, in order to allow you to use a <em>distinct</em> database and to create test objects, 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: #000000">#Preview {</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">! </span><span style="color: #795E26">ModelContainer</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: House.self,</span></span>
<span class="line"><span style="color: #000000">                                         </span><span style="color: #795E26">configurations</span><span style="color: #000000">: </span><span style="color: #795E26">ModelConfiguration</span><span style="color: #000000">(</span><span style="color: #795E26">isStoredInMemoryOnly</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: #795E26">MapView</span><span style="color: #000000">(</span><span style="color: #795E26">house</span><span style="color: #000000">: House.</span><span style="color: #795E26">forPreviewing</span><span style="color: #000000">())</span></span>
<span class="line"><span style="color: #000000">        .</span><span style="color: #795E26">modelContainer</span><span style="color: #000000">(container)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>I <em>believe</em> using an in-memory-only store will prevent it touching your actual app&#8217;s saved state, but I haven&#8217;t poked at it much.  I also believe you can use <a href="https://developer.apple.com/documentation/swiftdata/modelconfiguration/init(_:schema:url:allowssave:cloudkitdatabase:)" data-wpel-link="external" target="_blank" rel="external noopener">the more complicated configuration initialiser</a> to explicitly specify a URL, if you want a <em>persistent</em> test dataset that&#8217;s nonetheless separate from your app&#8217;s real dataset, but I haven&#8217;t played with it myself.</p>



<p>There are various tutorials online that go into more detail, e.g. <a href="https://www.hackingwithswift.com/about" data-wpel-link="external" target="_blank" rel="external noopener">Paul Hudson</a>&#8216;s <a href="https://www.hackingwithswift.com/quick-start/swiftdata/how-to-use-swiftdata-in-swiftui-previews" data-type="link" data-id="https://www.hackingwithswift.com/quick-start/swiftdata/how-to-use-swiftdata-in-swiftui-previews" data-wpel-link="external" target="_blank" rel="external noopener">How to use SwiftData in SwiftUI previews</a>.</p>



<h3 class="wp-block-heading">Apple&#8217;s documentation is wrong</h3>



<p>This one barely rates a mention since Apple&#8217;s documentation is pretty infamously unreliable (what little of it exists at all).  Still, it&#8217;s important to point out a couple of particularly glaring errors that will hit most SwiftData newbies:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<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">@Model</span></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">Trip</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"> name: </span><span style="color: #267F99">String</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> destination: </span><span style="color: #267F99">String</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> startDate: Date</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> endDate: Date</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> accommodation: Accommodation?</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>
<cite>Apple&#8217;s <a href="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches" data-type="link" data-id="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches" data-wpel-link="external" target="_blank" rel="external noopener">Preserving your app’s model data across launches</a></cite></blockquote>



<p>That is Apple&#8217;s <em>very first</em> code example in introducing SwiftData, and it&#8217;s wrong.  It has no initialiser, therefore you cannot actually use this <code>Trip</code> class.</p>



<p>Now, you might say that&#8217;s pedantic, but it&#8217;s actually quite pertinent &#8211; Apple <em>never</em> show a complete example of a <code>@Model</code> class, so they never show you how you&#8217;re supposed to initialise one, and thus they never even try to prevent people falling into pits such as <a href="#you-cannot-directly-initialise-bidirectional-relationship-properties">the aforementioned issues with arrays</a>.</p>



<p>Another poignant example:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<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">@Relationship</span><span style="color: #000000">(.</span><span style="color: #001080">cascade</span><span style="color: #000000">) </span><span style="color: #0000FF">var</span><span style="color: #000000"> accommodation: Accommodation?</span></span></code></pre></div>
<cite>Apple&#8217;s <a href="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches" data-type="link" data-id="https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches" data-wpel-link="external" target="_blank" rel="external noopener">Preserving your app’s model data across launches</a></cite></blockquote>



<p>That&#8217;s not valid; it doesn&#8217;t even compile.  The syntax <em>actually</em> 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">@Relationship</span><span style="color: #000000">(deleteRule: .</span><span style="color: #001080">cascade</span><span style="color: #000000">) </span><span style="color: #0000FF">var</span><span style="color: #000000"> accommodation: Accommodation?</span></span></code></pre></div>



<h4 class="wp-block-heading">Workarounds</h4>



<p>There are quite a few tutorials available online which provide an alternative onboarding guide, to Apple&#8217;s official documentation.  I did find a broad perusal of them useful in gleaning bits and pieces that Apple themselves don&#8217;t cover (or are wrong about).  Unfortunately many are essentially just copy-pastes or paraphrases of Apple&#8217;s documentation, and even those that aren&#8217;t tend to make the same mistakes.</p>



<p>But, since you&#8217;ve now read this post, you&#8217;re better equipped than most to try diving into SwiftData.</p>



<h2 class="wp-block-heading">Non-pitfalls</h2>



<p>Just as a bit of a bonus section, I want to call out one aspect of SwiftData which <em>actually</em> works as you might expect.</p>



<h3 class="wp-block-heading">You only need to register the root(s) of your model graph</h3>



<p>When setting up your model container, you don&#8217;t have to manually enumerate <em>every</em> model object you use, merely some root(s).  SwiftData automatically walks your class(es) member variables to find relationships to other <code>@Model</code> classes, and implicitly registers those too.  So 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">@main</span></span>
<span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Game</span><span style="color: #000000">: App {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> body: some Scene {</span></span>
<span class="line"><span style="color: #000000">        WindowGroup {</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #795E26">Map</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">                .</span><span style="color: #795E26">modelContainer</span><span style="color: #000000">(</span><span style="color: #795E26">for</span><span style="color: #000000">: [House.self])</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>You don&#8217;t need to list <code>Floor.self</code>, <code>Room.self</code>, etc.  You <em>can</em> &#8211; it doesn&#8217;t hurt anything, and might make your app a little more robust against future refactors &#8211; but it&#8217;s nice that you don&#8217;t have to.</p>



<h2 class="wp-block-heading">Bonus: TablePlus recommendation</h2>



<p>It&#8217;s unfortunately critical, when working with SwiftData (or Core Data), to be able to view &amp; edit the underlying SQLite database.  You can do that out-of-the-box on your Mac using just the built-in <code>sqlite3</code> CLI, but that&#8217;s pretty awkward.</p>



<p>There are <em>many</em> GUI apps for working with SQLite databases.  Quite a few are free.  Most are functional but clumsy.</p>



<p>Years ago &#8211; when I was mostly working with MySQL &#8211; I came across <a href="https://tableplus.com" data-type="link" data-id="https://tableplus.com" data-wpel-link="external" target="_blank" rel="external noopener">TablePlus</a>.  <a href="https://tableplus.com/pricing" data-wpel-link="external" target="_blank" rel="external noopener">It&#8217;s a bit pricey</a> at $90 up front &#8211; plus $50 per subsequent year if you want to keep getting software updates &#8211; but it&#8217;s by far the best database client I&#8217;ve found, on any platform.  It&#8217;s easily the most native-feeling on a Mac, even though it&#8217;s cross-platform (macOS, iOS / iPadOS, Linux, and Windows!), and one of the fastest.  It doesn&#8217;t <em>quite</em> have all the features &#8211; I do sometimes fire up <a href="https://www.mysql.com/products/workbench/" data-type="link" data-id="https://www.mysql.com/products/workbench/" data-wpel-link="external" target="_blank" rel="external noopener">MySQL Workbench</a> to access its query performance visualisation &#8211; but it covers 99% of what I need.</p>



<p>My only notable complaint with it is that it has historically been a tad buggy.  Not dramatically, but consistently.  That said, I only just got back into it as a result of this SwiftData work &#8211; and it&#8217;s worked flawlessly so far &#8211; so it may well have upped its game since I last properly used it a couple of years ago. 🤞</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swiftdata-pitfalls/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5378</post-id>	</item>
		<item>
		<title>getifaddrs never specifies broadcast addresses</title>
		<link>https://wadetregaskis.com/getifaddrs-never-specifies-broadcast-addresses/</link>
					<comments>https://wadetregaskis.com/getifaddrs-never-specifies-broadcast-addresses/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 27 Apr 2023 19:25:36 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[getifaddrs]]></category>
		<category><![CDATA[macOS]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=5250</guid>

					<description><![CDATA[Apple &#8220;Feedback&#8221; #12149764. According to man 3 getifaddrs: The ifa_dstaddr field references the destination address on a P2P interface, if one exists, otherwise it contains the broadcast address. In my testing the ifa_dstaddr field is never non-null. I&#8217;m not sure I have any suitably configured P2P interfaces, but I definitely have interfaces with broadcast capabilities&#8230; <a class="read-more-link" href="https://wadetregaskis.com/getifaddrs-never-specifies-broadcast-addresses/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Apple &#8220;Feedback&#8221; #12149764.</p>



<p>According to <code>man 3 getifaddrs</code>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The ifa_dstaddr field references the destination address on a P2P interface, if one exists, otherwise it contains the broadcast address.</p>
</blockquote>



<p>In my testing the <code>ifa_dstaddr</code> field is never non-null.  I&#8217;m not sure I have any suitably configured P2P interfaces, but I definitely have interfaces with broadcast capabilities and <code>getifaddrs</code> reports them as having no broadcast address (even though it correctly notes they support broadcasting, in <code>ifa_flags</code>).</p>



<p>This is on macOS Ventura 13.3.1.  I have no idea if this applies to other versions of macOS, or other OS&#8217;s.</p>



<p>It appears there&#8217;s a workaround, though &#8211; the broadcast address is deterministic based on the IP address &amp; netmask, <a rel="noreferrer noopener external" href="https://en.wikipedia.org/wiki/Broadcast_address#IP_networking" data-type="URL" data-id="https://en.wikipedia.org/wiki/Broadcast_address#IP_networking" target="_blank" data-wpel-link="external">according to Wikipedia</a>, so you don&#8217;t technically need <code>getifaddrs</code> to spell it out for you.  That&#8217;s for AF_INET (IPv4) networks.  IPv6 doesn&#8217;t really have the same notion of a broadcast address anyway, and for other network types I have no idea if the concept applies or if there&#8217;s workarounds for this <code>getifaddr</code> bug.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/getifaddrs-never-specifies-broadcast-addresses/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5250</post-id>	</item>
		<item>
		<title>getifaddrs returns truncated sockaddr_in&#8217;s for AF_INET ifa_netmasks</title>
		<link>https://wadetregaskis.com/getifaddrs-returns-truncated-sockaddr_ins-for-af_inet-ifa_netmasks/</link>
					<comments>https://wadetregaskis.com/getifaddrs-returns-truncated-sockaddr_ins-for-af_inet-ifa_netmasks/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 27 Apr 2023 18:57:02 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[getifaddrs]]></category>
		<category><![CDATA[macOS]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=5247</guid>

					<description><![CDATA[Apple &#8220;Feedback&#8221; #12149675. Some netmasks returned by getifaddrs have family of AF_INET yet a length less than sizeof(sockaddr_in), e.g. 5, 6, 7, or 8. On macOS Ventura 13.3.1, at least. It looks like it&#8217;s actually allocating only eight bytes for the ifa_netmask (not the 16 that is the size of sockaddr_in per MacOSX13.3.sdk/usr/include/netinet/in.h), as it&#8230; <a class="read-more-link" href="https://wadetregaskis.com/getifaddrs-returns-truncated-sockaddr_ins-for-af_inet-ifa_netmasks/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Apple &#8220;Feedback&#8221; #12149675.</p>



<p>Some netmasks returned by <code>getifaddrs</code> have family of <code>AF_INET</code> yet a length less than <code>sizeof(sockaddr_in)</code>, e.g. 5, 6, 7, or 8.  On macOS Ventura 13.3.1, at least.</p>



<p>It looks like it&#8217;s actually allocating only eight bytes for the <code>ifa_netmask</code> (not the 16 that is the size of <code>sockaddr_in</code> per <code>MacOSX13.3.sdk/usr/include/netinet/in.h</code>), as it clearly has a different <code>sockaddr_in</code> immediately after it, e.g.:</p>



<pre class="wp-block-preformatted">sockaddr
  - sa_len : 5
  - sa_family : 2
  ▿ sa_data : 14 elements
    - .0 : 0
    - .1 : 0
    - .2 : 255
    - .3 : 0
    - .4 : 0
    - .5 : 0
    - .6 : 16 // This is obviously the start of the next sockaddr_in; a correct entry with length of 16.
    - .7 : 2
    - .8 : 0
    - .9 : 0
    - .10 : 127
    - .11 : 0
    - .12 : 0
    - .13 : 1</pre>



<p>It&#8217;s thus clear that it&#8217;s both wrong about the actual length in memory and it is indeed truncating the <code>sockaddr_in</code> structure, although it looks like it&#8217;s actually including the full address (the lo0 netmask in the above example is indeed 255.0.0.0).</p>



<p>Here&#8217;s another example, where the netmask is 255.255.255.0:</p>



<pre class="wp-block-preformatted">▿ sockaddr
  - sa_len : 7
  - sa_family : 2
  ▿ sa_data : 14 elements
    - .0 : 0
    - .1 : 0
    - .2 : 255
    - .3 : 255
    - .4 : 255
    - .5 : 0
    - .6 : 16 // Again, clearly the start of the next sockaddr_in.
    - .7 : 2
    - .8 : 0
    - .9 : 0
    - .10 : 192
    - .11 : 168
    - .12 : 0
    - .13 : 24</pre>



<p>So you can see it&#8217;s:</p>



<ul class="wp-block-list">
<li>Only allocating up to the address of <code>sockaddr_in</code>, not counting the padding (so <code>sizeof(sockaddr_in)</code>, <code>MemoryLayout&lt;sockaddr_in>.size</code>, etc disagree with what <code>getifaddrs</code> is doing), and</li>



<li>Weirdly truncating the stated length (<code>sin_len</code>) to the end of the non-zero bytes in the netmask.</li>
</ul>



<p>This is undocumented (well, all these data structures are completely undocumented 😔) and is all very messy.  <code>getifaddrs</code> should just provide the full <code>sockaddr_in</code> without any of this weirdness, so that users which blindly cast to <code>sockaddr_in</code> (without checking <code>sin_len</code>) don&#8217;t risk segmentation faults or reading corrupt data.</p>



<p>I have no idea why it does this <em>only</em> for <code>ifa_netmask</code>, not <code>ifa_addr</code> or <code>ifa_dstaddr</code>.  That discrepancy just makes it even more troublesome.</p>



<p>As far as I can tell there are no similar issues with <code>AF_INET6</code> netmasks, although without knowing under what criteria it&#8217;s truncating, I can&#8217;t be sure I have all relevant examples covered on the happenstance networks of my laptop.</p>



<p>For these systems, there&#8217;s seemingly not much you can do… it seems you have to hard-code the assumption that <code>ifa_netmask</code> contains exactly &amp; only the first eight bytes of the <code>sockaddr_in</code>.  You have to completely ignore <code>sin_len</code> (at least if it&#8217;s less than eight).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/getifaddrs-returns-truncated-sockaddr_ins-for-af_inet-ifa_netmasks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5247</post-id>	</item>
		<item>
		<title>Nikon Z7 very first impressions</title>
		<link>https://wadetregaskis.com/nikon-z7-very-first-impressions/</link>
					<comments>https://wadetregaskis.com/nikon-z7-very-first-impressions/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 03 Oct 2018 16:32:33 +0000</pubDate>
				<category><![CDATA[Photography]]></category>
		<category><![CDATA[Reviews]]></category>
		<category><![CDATA[D500]]></category>
		<category><![CDATA[Nikon]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[SnapBridge]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[Z7]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=4200</guid>

					<description><![CDATA[This is in the context of coming from a D500 (and a number of DX DSLRs prior to that), and is based only on the first hour or so of using it.]]></description>
										<content:encoded><![CDATA[
<p>This is in the context of coming from a D500 (and a number of DX DSLRs prior to that), and is based only on the first hour or so of using it.</p>



<ul class="wp-block-list">
<li>No XQD card included in the U.S.A. &nbsp;This is disappointing, since it appears that every other country on the planet is getting XQD cards included in theirs, to a value of ~$150USD, so it feels a little mean that the U.S.A. is getting screwed. &nbsp;Especially since by all accounts U.S. shipments of the Z7 were delayed by nearly a week compared to most of the rest of the world. &nbsp;It also seems like simply a bad idea on Nikon&#8217;s behalf &#8211; very few people will have an XQD card already (luckily I have one and only one, from my D500), so Nikon&#8217;s running a real risk that a lot of people will open their new shiny only to realise that there&#8217;s no memory card they can use in it, and acquiring one is going to be hard (local retailers don&#8217;t seem to stock them consistently) and&nbsp;<em>very</em> expensive (XQD cards are currently selling at all-time high prices, despite there being more brands selling them than ever, and more demand than ever, and commodity NAND flash being at its lowest price in a long time… grrr).</li>



<li>Autofocus&nbsp;<em>really</em> struggles in &#8220;low light&#8221; (e.g. a well-lit restaurant at night), where the D500 would have no problems at all, using the kit 24-70/4 lens. &nbsp;In fact at first I thought the camera was faulty, because I could not for the life of me get it to take a photo, of anything. &nbsp;Eventually I realised it was defaulting, out of the factory, to Focus-priority, and once I switched to Release-priority it started working. &nbsp;But focus was missed most of the time, usually significantly (e.g. headshots had&nbsp;<em>no</em> part of the head in focus most of the time; at best the ears). &nbsp;This was true irrespective of focus mode. &nbsp;In fairness, the D500 is over-confident in its autofocusing abilities &#8211; in similar conditions it would also miss focus in many shots, despite claiming it had quickly acquired focus. &nbsp;Note also that &#8220;Low Light AF&#8221; makes no apparent difference, neither in autofocus speed, ability, nor accuracy.</li>



<li>It&#8217;s a very small camera. &nbsp;It has some density to it, so it doesn&#8217;t necessarily feel cheap or plastic, but ergonomically it&#8217;s not great. &nbsp;The D500 is a much better camera ergonomically (as is the D850, being a very similar design). &nbsp;The Z7 in principle has an interesting advantage which is the ability to do everything through the viewfinder, but the camera is so small and squished that having your face up against it, to look through the viewfinder, makes it very difficult to use any of the buttons or the D-pad. &nbsp;It&#8217;s doable, but it&#8217;s awkward and I won&#8217;t be making a habit of it. &nbsp;The D500 / D850 / etc are actually much more usable when your eye is at the viewfinder, control-wise.</li>



<li>Button placement is a bit weird. &nbsp;The D500 / D850 / etc have a superior layout &#8211; and more buttons. &nbsp;My thumb rests over the &#8216;Disp&#8217; button by default, not the AF-ON where it should, because the camera is so squished that the &#8216;Disp&#8217; button &#8211; relative to the hand grip &amp; other buttons &#8211; is basically where AF-ON is on the D500 / D850 / etc. &nbsp;I hope I&#8217;ll get used to it, but it is definitely more awkward to hold the Z7 with your hand on its AF-ON button, because your entire hand and fingers are all relatively far to the right edge of the camera, putting a lot more torque on your grip in order to hold the camera flat.</li>



<li>The function buttons on the lens are actually an improvement over the equivalents on Nikon&#8217;s DSLRs &#8211; they naturally rest under two of my fingers, more or less, making them easier to use.</li>



<li>The mount diameter is&nbsp;<em>way</em> bigger than the old F-mount. &nbsp;Not that it&#8217;s intellectually a surprise, but upon first seeing it in person I was irrationally gleeful.</li>



<li>Image quality vs the D500 in low light appears mixed… even by the most optimistic objective measures the D850 (and by extension Z7) are only about 2/3rds of a stop better than the D500 at ISOs 100 and above (the ISO 64 base does push the advantage to one full stop in principle, vs the D500 at ISO 100). &nbsp;However, given the recent, disappointing revelations from DPReview on the nasty banding exhibited by the Z7, my fear is that the D500 will actually turn out to have&nbsp;<em>better</em> image quality in many situations (i.e. anything with significant dynamic range). &nbsp;This is obviously very disappointing for a very expensive, top-of-the-line, brand new camera with an FX vs DX sensor size advantage.</li>



<li>Contrary to some reporting, and some of Nikon&#8217;s own misleading product material, 100fps &amp; 120fps 1080p video is&nbsp;<em>only</em> available from a ~DX crop region.</li>



<li>Focus peaking is very difficult to actually get to work. &nbsp;It took me nearly an hour to figure out how &#8211; it only appears if (a) you have AF-ON&nbsp;<em>held down,&nbsp;</em>(b) you move the manual focus ring on the lens a significant distance in order to engage MF override, and (c) you have a lot of light and contrast in the scene. &nbsp;In low light, or scenes with low contrast, it simply doesn&#8217;t show any peaking, even on the most sensitive setting, and provides no indication why. &nbsp;This is all very unfortunate, as competing focus peaking systems in every other mirrorless camera I&#8217;ve ever used all perform much more reliably, easily, and consistently than the Z7&#8217;s system does. &nbsp;e.g. the Sony a7R II&#8217;s focus peaking was excellent in practice for ensuring correct focus, whereas my tests so far with the Z7, when it bothers to work at all, have shown that it&#8217;s not accurate nor clean enough for me to actually get correct focus most of the time. &nbsp;It&#8217;s much faster &amp; more reliable to just engage image zoom and focus without peaking. &nbsp;Also, peaking doesn&#8217;t work when zoomed in.</li>



<li>The focus ring on the 24-70/4 is awkwardly placed &#8211; it&#8217;s way too close to the camera body, which is very thin to begin with, so it feels like you&#8217;re picking your nose when you operate it. &nbsp;Even with a light lens like the 24-70/4, holding the lens by the focus ring makes the entire thing very front-heavy. &nbsp;The focus ring is also very thin, making it a bit difficult to find and get a good hold on.</li>



<li>Being able to zoom in, in the viewfinder, is awesome. &nbsp;I&#8217;ve used this previously on other mirrorless systems and know from that experience that it&#8217;ll be immensely valuable in getting focus correct. &nbsp;It also works pretty intuitively &#8211; e.g. it zooms in on the selected focus point, naturally &#8211; and can be assigned to most (but bizarrely not all) the configurable buttons for easy toggling.</li>



<li>I miss the Nikon rubber eye-cup add-on I applied to my D500. &nbsp;The Z7&#8217;s naked viewfinder, while slightly rubbery, is very hard in comparison, and &#8211; being &#8211; rectangular &amp; flat &#8211; doesn&#8217;t fit any human face I&#8217;ve ever encountered. &nbsp;No different from most cameras, of course &#8211; I just hope Nikon release an equivalent eye-cup for the Z7 soon (though I worry, from looking at the viewfinder assembly, that there&#8217;s no apparent way to pull it apart, attach anything to it, etc).</li>



<li>On first use the battery jammed in the battery slot, requiring some shaking and application of fingernails to force it out. &nbsp;Very weird &#8211; I&#8217;ve never encountered this in many years &amp; many Nikon cameras. &nbsp;It hasn&#8217;t done it since… yet.</li>



<li>The box it comes in is surprisingly large given it&#8217;s a small camera &amp; lens. &nbsp;Much bigger than the equivalent box for the D500, or any of Nikon&#8217;s consumer DSLRs.</li>



<li>The fully electronic (&#8220;silent&#8221;) shutter is very nice. &nbsp;The D500 is a 5 AM garbage truck in comparison &#8211; it has always bothered me using the D500 in any even remotely quiet environment.</li>



<li>Viewfinder blackout is so-so. &nbsp;While I&#8217;d seen videos on YouTube demonstrating it in various modes etc, in practice I find it&#8217;s much more difficult than I expected to track moving subjects when shooting at anything approaching the maximum frame rate (8 FPS). &nbsp;The D500, despite having significant black-out itself vs the D5, is notably superior than the Z7.</li>



<li>SnapBridge is stupidly hard to get to work &#8211; mainly in the initial pairing. &nbsp;It took me multiple tries and about an hour overall to get it to finally pair to my iPhone. &nbsp;It requires an extremely precise, pedantic, and rather long sequence of steps in order to get it to pair, and some of those steps are not documented by Nikon. &nbsp;I vaguely recall it being similarly bad with the D500 when I first got it &#8211; thankfully it&#8217;s a process that only needs doing once per camera body, in principle.</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nikon-z7-very-first-impressions/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2018/10/Z7.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">4200</post-id>	</item>
		<item>
		<title>iOS Family Sharing users cannot mix authentication schemes</title>
		<link>https://wadetregaskis.com/ios-family-sharing-users-cannot-mix-authentication-schemes/</link>
					<comments>https://wadetregaskis.com/ios-family-sharing-users-cannot-mix-authentication-schemes/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 15 Apr 2017 19:51:04 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[AppleID]]></category>
		<category><![CDATA[authentication]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Family Sharing]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[iPad]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Two-factor]]></category>
		<category><![CDATA[Two-step]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[What do you want?]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=3898</guid>

					<description><![CDATA[Apple supports two styles of two-factor authentication,&#160;that they call (and distinguish as) &#8220;two-step&#8221; vs &#8220;two-factor&#8221;. &#160;&#8220;Two-step&#8221; is their older method, though functionally they&#8217;re basically equivalent. If you have multiple accounts on a Family Sharing arrangement, and some use &#8220;two-factor&#8221; while others use &#8220;two-step&#8221;, you&#8217;re in for&#160;a bag of hurt. For example, any time you change&#8230; <a class="read-more-link" href="https://wadetregaskis.com/ios-family-sharing-users-cannot-mix-authentication-schemes/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Apple supports two styles of two-factor authentication,&nbsp;that they call (and distinguish as) &#8220;two-step&#8221; vs &#8220;two-factor&#8221;. &nbsp;&#8220;Two-step&#8221; is their older method, though functionally they&#8217;re basically equivalent.</p>



<p>If you have multiple accounts on a Family Sharing arrangement, and some use &#8220;two-factor&#8221; while others use &#8220;two-step&#8221;, you&#8217;re in for&nbsp;a bag of hurt.</p>



<p>For example, any time you change the password on any of the non-master accounts, you&#8217;ll have to reauthorise all devices on that account with the master purchaser. &nbsp;You&#8217;ll be prompted, when trying to download apps or purchase anything etc, with a dialog saying &#8220;Your Family Organizer, [foo], must enter the security code for their payment method&#8221;, asking for some kind of input. &nbsp;There is literally nothing you can enter there that will make it work. &nbsp;Not the password for any of the relevant Apple IDs, not any security codes for any credit cards, nada.</p>



<p>The problem is that it&#8217;s asking for a verification code that you can only create on a device which has &#8220;two-factor&#8221; authentication enabled. &nbsp;Compare for example what you see with &#8220;two-factor&#8221; authentication enabled on your iDevice:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><a href="https://wadetregaskis.com/wp-content/uploads/2017/04/Two-factor.webp" data-wpel-link="internal"><img loading="lazy" decoding="async" width="1536" height="2048" src="https://wadetregaskis.com/wp-content/uploads/2017/04/Two-factor.webp" alt="Screenshot of two-factor authentication enabled in iOS account settings" class="wp-image-3899" style="width:768px" srcset="https://wadetregaskis.com/wp-content/uploads/2017/04/Two-factor-768x1024@2x.webp 1536w, https://wadetregaskis.com/wp-content/uploads/2017/04/Two-factor-384x512@2x.webp 768w, https://wadetregaskis.com/wp-content/uploads/2017/04/Two-factor-192x256.webp 192w, https://wadetregaskis.com/wp-content/uploads/2017/04/Two-factor-384x512.webp 384w" sizes="auto, (max-width: 1536px) 100vw, 1536px" /></a></figure>
</div>


<p>Versus what you see with &#8220;two-step&#8221;:</p>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><a href="https://wadetregaskis.com/wp-content/uploads/2017/04/Two-step.webp" data-wpel-link="internal"><img loading="lazy" decoding="async" width="1536" height="2048" src="https://wadetregaskis.com/wp-content/uploads/2017/04/Two-step.webp" alt="Screenshot of two-step authentication enabled in iOS account settings" class="wp-image-3900" style="width:768px" srcset="https://wadetregaskis.com/wp-content/uploads/2017/04/Two-step-768x1024@2x.webp 1536w, https://wadetregaskis.com/wp-content/uploads/2017/04/Two-step-384x512@2x.webp 768w, https://wadetregaskis.com/wp-content/uploads/2017/04/Two-step-192x256.webp 192w, https://wadetregaskis.com/wp-content/uploads/2017/04/Two-step-384x512.webp 384w" sizes="auto, (max-width: 1536px) 100vw, 1536px" /></a></figure>
</div>


<p>That &#8220;Get Verification Code&#8221; &#8220;button&#8221; is what you&#8217;re looking for. &nbsp;As you can see, it simply doesn&#8217;t exist with &#8220;two-step&#8221; authentication&nbsp;enabled.</p>



<p>The only solution &#8211; to allow your family members to download apps, purchase music / videos / books / etc, or pretty much do anything else on their iDevices &#8211; is to force the master account over to &#8220;two-factor&#8221; authentication.</p>



<p>To do this, you have to go to&nbsp;<a href="https://appleid.apple.com/" data-wpel-link="external" target="_blank" rel="external noopener">https://appleid.apple.com/</a>&nbsp;and turn off &#8220;two-step&#8221; authentication (which will require you to complete some stupid &#8216;security&#8217; questions). &nbsp;You cannot turn off &#8220;two-step&#8221; authentication from any of your actual iDevices&#8217; Settings apps.</p>



<p>Then, stupidly, you can&#8217;t actually enable &#8220;two-factor&#8221; authentication from that same website. &nbsp;That can only be done in the Settings app on one of your iDevices &#8211; by (in iOS 10.3 or later) going into Settings ➜ &lt;your name at the top of the list&gt;&nbsp;➜ Password &amp; Security.</p>



<p>There&#8217;s no way to enable &#8220;two-step&#8221; authentication anymore. &nbsp;And not having any form of two-factor authentication enabled is a very bad idea. &nbsp;So if any of your family&#8217;s accounts have &#8220;two-factor&#8221; authentication enabled, you basically have to switch&nbsp;to &#8220;two-factor&#8221; on <em>all</em> of them.</p>



<p>Which would be broadly fine, if Apple hadn&#8217;t made it so needlessly&nbsp;complicated, and the two systems so&nbsp;incompatible that their own software can&#8217;t figure out what&#8217;s going on.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/ios-family-sharing-users-cannot-mix-authentication-schemes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3898</post-id>	</item>
		<item>
		<title>Rotated Windows</title>
		<link>https://wadetregaskis.com/rotated-windows/</link>
					<comments>https://wadetregaskis.com/rotated-windows/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 26 Feb 2017 08:08:13 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Creative]]></category>
		<category><![CDATA[BOMGAR]]></category>
		<category><![CDATA[class-dump]]></category>
		<category><![CDATA[Claus Atzenbeck]]></category>
		<category><![CDATA[CoreGraphicsServices]]></category>
		<category><![CDATA[Growl]]></category>
		<category><![CDATA[hack]]></category>
		<category><![CDATA[reverse engineering]]></category>
		<category><![CDATA[Rotated Windows]]></category>
		<category><![CDATA[sitx]]></category>
		<category><![CDATA[Slashdot]]></category>
		<category><![CDATA[StuffIt]]></category>
		<category><![CDATA[Tiger]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=3863</guid>

					<description><![CDATA[I&#8217;d forgotten about this until I stumbled across a reference to it again recently. This was a little hack I worked on back in 2004, with Mac OS X Tiger (10.4). &#160;Yes, kids, macOS was called Mac OS X back in ye Olden Times. Wow, Slashdot looked even uglier than I remember, back then. &#160;Though&#8230; <a class="read-more-link" href="https://wadetregaskis.com/rotated-windows/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;d forgotten about this until I stumbled across a reference to it again recently.</p>



<p>This was a little hack I worked on back in 2004, with Mac OS X Tiger (10.4). &nbsp;Yes, kids, macOS was called Mac OS X back in ye Olden Times.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://wadetregaskis.com/wp-content/uploads/2017/02/Rotated-Windows-example.webp" data-wpel-link="internal"><img loading="lazy" decoding="async" width="1024" height="768" src="https://wadetregaskis.com/wp-content/uploads/2017/02/Rotated-Windows-example.webp" alt="Rotated Windows example screenshot" class="wp-image-3886" srcset="https://wadetregaskis.com/wp-content/uploads/2017/02/Rotated-Windows-example-512x384@2x.webp 1024w, https://wadetregaskis.com/wp-content/uploads/2017/02/Rotated-Windows-example-256x192.webp 256w, https://wadetregaskis.com/wp-content/uploads/2017/02/Rotated-Windows-example-512x384.webp 512w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>
</div>


<p>Wow, Slashdot looked even uglier than I remember, back then. &nbsp;Though amusingly my daily reading list hasn&#8217;t changed substantially &#8211; it still features Slashdot and MacSurfer&#8217;s Headline News.</p>



<p>Also… 1024 x 768. &nbsp;That&#8217;s just over 5% of the resolution of my current display (27&#8243; Retina iMac). &nbsp;It&#8217;s nearly as big as my iPhone 6s&#8217;s screen.</p>



<p>Man, do I <em>not</em> miss those shitty old monitors.</p>



<p>I don&#8217;t recall what the exact impetus was for the project. &nbsp;I do recall that I was spurred on by Claus Atzenbeck, who was doing some kind of academic work into graphical user interfaces and, IIRC, wanted a way to explore window rotation and general manipulation in a real OS.</p>



<p><a href="https://web.archive.org/web/20170421081203/https://www.atzenbeck.de/" data-wpel-link="external" target="_blank" rel="external noopener">Claus&#8217;s personal website</a> still exists, all these years later, though alas <a href="https://web.archive.org/web/20100225222207/http://www.atzenbeck.de/research/wildWindows/" data-wpel-link="external" target="_blank" rel="external noopener">the link</a> to his relevant research is now broken.</p>



<p>What reminded me of this was finding an attribution to me in a header file that was associated with the project &#8211; CoreGraphicsServices.h. &nbsp;This was something I generated (presumably with the help of <a href="http://stevenygard.com/projects/class-dump/" data-wpel-link="external" target="_blank" rel="external noopener">class-dump</a> or similar) from the CoreGraphicsServices framework, and then partially reverse-engineered (in the sense of figuring out parameter types, function prerequisites, etc). &nbsp;It&#8217;s what was necessary to find &amp; use the private APIs for doing window geometry manipulation.</p>



<p>And the only reason my name is on it is because I splatted a 3-clause BSD license into the header file I made, which in hindsight seems highly dubious since the APIs themselves are owned by Apple (insofar as one can &#8216;own&#8217; APIs, I guess…).</p>



<p>A quick web search reveals a few more mentions:</p>



<ul class="wp-block-list">
<li>The aforementioned header is apparently <a href="https://github.com/growl/growl/blob/a8c142ffc90a326a77cbe05962e537b58a91d225/Core/Source/CoreGraphicsServices.h" data-wpel-link="external" target="_blank" rel="external noopener">used by Growl</a>.</li>



<li>&#8220;BOMGAR&#8221;, some kind of remote computer support software, <a href="https://web.archive.org/web/20150115034157/https://www.bomgar.com/open-source-statement" data-wpel-link="external" target="_blank" rel="external noopener">apparently uses the header too</a>.</li>



<li>As does <a href="https://www.marsthemes.com/crystalclear/documentation/index.html" data-wpel-link="external" target="_blank" rel="external noopener">something on marsthemes.com</a>, though at time of writing that website has been largely destroyed for some reason.</li>



<li>This one particularly amuses me &#8211; a <a href="https://www.cocoabuilder.com/archive/cocoa/112898-expose-api.html" data-wpel-link="external" target="_blank" rel="external noopener">brief thread on cocoa-dev@</a> about the header, in which John C. Randolph categorically takes no particular position on the hack. :)</li>
</ul>



<p>The source &amp; other paraphernalia were <a href="https://web.archive.org/web/20051119180659/https://homepage.cs.latrobe.edu.au/wjtregaskis/Rotated%20Windows.sitx" data-wpel-link="external" target="_blank" rel="external noopener">originally posted on my La Trobe University student web hosting account</a>, though of course that&#8217;s long gone. &nbsp;<a href="https://wadetregaskis.com/MobileMe/Public/Rotated%20Windows.sitx" data-wpel-link="internal">Here&#8217;s the original StuffIt archive</a>, if you&#8217;re interested. &nbsp;I don&#8217;t actually know if it&#8217;s the very latest version &#8211; I do still have the project in full &#8211; but it&#8217;s the latest version I ever published, AFAIR.</p>



<p>I leave it as an exercise to the reader on how to decompress StuffIt files in this day and age. :)</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/rotated-windows/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2017/02/Rotated-Windows-example.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">3863</post-id>	</item>
		<item>
		<title>Undocumented Swift conditional compilation macros</title>
		<link>https://wadetregaskis.com/undocumented-swift-conditional-compilation-macros/</link>
					<comments>https://wadetregaskis.com/undocumented-swift-conditional-compilation-macros/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 14 Jan 2017 21:45:55 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=3856</guid>

					<description><![CDATA[swift/lib/Basic/LangOptions.cpp has most of the conditional compilation macros (called &#8220;Language Options&#8221; in the compiler internally).  Notably the swift() version macro is absent, and doesn&#8217;t seem to be defined anywhere… At time of writing the two undocumented additions, to the os(), arch(), and swift() set, are _endian() and _runtime(). I have no idea if they&#8217;re useful&#8230; <a class="read-more-link" href="https://wadetregaskis.com/undocumented-swift-conditional-compilation-macros/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[<p><a href="https://github.com/apple/swift/blob/bbecb33d13bf91696ea917f1b2a29f133168e67b/lib/Basic/LangOptions.cpp" data-wpel-link="external" target="_blank" rel="external noopener">swift/lib/Basic/LangOptions.cpp</a> has most of the conditional compilation macros (called &#8220;Language Options&#8221; in the compiler internally).  Notably the <code>swift()</code> version macro is absent, and doesn&#8217;t seem to be defined anywhere…</p>
<p>At time of writing the two undocumented additions, to the <code>os()</code>, <code>arch()</code>, and <code>swift()</code> set, are <code>_endian()</code> and <code>_runtime()</code>.</p>
<p>I have no idea if they&#8217;re useful or not &#8211; does anyone have to care about CPU endianness these days, really? &#8211; but there they are.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/undocumented-swift-conditional-compilation-macros/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3856</post-id>	</item>
		<item>
		<title>#if DEBUG in Swift</title>
		<link>https://wadetregaskis.com/if-debug-in-swift/</link>
					<comments>https://wadetregaskis.com/if-debug-in-swift/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 14 Jan 2017 21:15:33 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[clang]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=3853</guid>

					<description><![CDATA[Sigh. The Swift team give an impeccable impression of a group of people who&#8217;ve never actually tried to use Swift. An incredibly basic compiler task is to provide code a way to distinguish between debug &#38; release builds, in order that it can&#160;behave accordingly (e.g. change the default logging verbosity, change asserts from fatal to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/if-debug-in-swift/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Sigh.</p>



<p>The Swift team give an impeccable impression of a group of people who&#8217;ve never actually tried to use Swift.</p>



<p>An incredibly basic compiler task is to provide code a way to distinguish between debug &amp; release builds, in order that it can&nbsp;behave accordingly (e.g. change the default logging verbosity, change asserts from fatal to non-fatal, etc).</p>



<p>Long story short there is no way to do this that works correctly with <code>swift build</code>.</p>



<p>You can make it work with Xcode <em>only</em>&nbsp;by way of a simple workaround &#8211; you manually define a custom Swift flag in your target&#8217;s settings (<a href="https://stackoverflow.com/questions/24003291/ifdef-replacement-in-the-swift-language/36502874#36502874" data-wpel-link="external" target="_blank" rel="external noopener">here&#8217;s one</a> of a bajillion explanations of how do this). &nbsp;You end up with basically identical code to other C-family languages, 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">#</span><span style="color: #AF00DB">if</span><span style="color: #0000FF"> DEBUG</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> logVerbosity = </span><span style="color: #098658">1</span></span>
<span class="line"><span style="color: #0000FF">#</span><span style="color: #AF00DB">else</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> logVerbosity = </span><span style="color: #098658">0</span></span>
<span class="line"><span style="color: #0000FF">#</span><span style="color: #AF00DB">endif</span></span></code></pre></div>



<p>But there is no way, when using <code>swift build</code>, to specify custom Swift flags in your package config.</p>



<p>You can specify them manually with every single <code>swift build</code> invocation, e.g.:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><code>swift build -c debug -Xswiftc '-DDEBUG'</code></p>
</blockquote>



<p>But now you have extra work and the possibility of screwing it up (e.g. omitting the flag, or mismatching it to your actual build style).</p>



<p>The closest you can get is to use some undocumented, hidden Swift internal library functions:</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">_isDebugAssertConfiguration</span><span style="color: #000000">() -&gt; </span><span style="color: #267F99">Bool</span></span>
<span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">_isFastAssertConfiguration</span><span style="color: #000000">() -&gt; </span><span style="color: #267F99">Bool</span></span></code></pre></div>



<p>These are defined in <a href="https://github.com/apple/swift/blob/adc54c8a4d13fbebfeb68244bac401ef2528d6d0/stdlib/public/core/AssertCommon.swift" data-wpel-link="external" target="_blank" rel="external noopener">swift/stdlib/public/core/AssertCommon.swift</a>.</p>



<p>Only the first is likely to be useful. &nbsp;The second applies in the case where you&#8217;re building not just for release but&nbsp;<em>unchecked</em> (<code>-Ounchecked</code>). &nbsp;If you just want to conditionalise on release builds generally, you have to do <code>!_isDebugAssertConfiguration()</code>.</p>



<p>The additional problem with this approach is that these are then, in your code,&nbsp;<em>runtime</em> checks. &nbsp;And the compiler then thinks it&#8217;s being helpful by pointing out that some of your code that uses them is unreachable.</p>



<p>And of course Swift has absolutely no way to silence compiler warnings, or otherwise tell the compiler not to trust its reachability checks.</p>



<p>Sigh.</p>



<h2 class="wp-block-heading">Update (February 2018)</h2>



<p>While the above method &#8211; using the <code>_isDebugAssertConfiguration()</code> method &#8211; does still work at time of writing, in Swift 4.1, there are some marginally better ways now available. &nbsp;There&#8217;s still not a&nbsp;<em>proper</em> solution, infuriatingly, but with some creativity, as you&#8217;ll see momentarily, you can get pretty close to expected functionality.</p>



<h3 class="wp-block-heading">First alternative</h3>



<p>This will only suit some cases, but its relative cleanliness &amp; simplicity makes it appealing when it is an option.</p>



<p>You can use&nbsp;<code>#if targetEnvironment(simulator)</code>. &nbsp;This of course only distinguishes between the simulator and a real iDevice environment, though that can often be useful too, perhaps orthogonally to DEBUG vs RELEASE concepts.</p>



<h3 class="wp-block-heading">Second alternative</h3>



<p>Wrap all uses of <code>_isDebugAssertConfiguration()</code> inside a minimal set of functions. &nbsp;All this gets you is a reduced (and fixed) number of unreachable code warnings, though.</p>



<p>For 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">func</span><span style="color: #000000"> </span><span style="color: #795E26">inDebugBuilds</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">: () -&gt; </span><span style="color: #267F99">Void</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: #795E26">_isDebugAssertConfiguration</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">code</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">func</span><span style="color: #000000"> </span><span style="color: #795E26">inReleaseBuilds</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">: () -&gt; </span><span style="color: #267F99">Void</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: #795E26">_isDebugAssertConfiguration</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">code</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>You can then use these in a fairly streamlined way:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">inDebugBuilds {</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, I only greet in debug builds!&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">inReleaseBuilds {</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;While I only greet in release builds - aloha!&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h3 class="wp-block-heading">Third alternative</h3>



<p>It&#8217;s possible to do one better than the above, and eliminate those unreachable code warnings entirely. &nbsp;Plus, doing so actually removes any use of unofficial APIs… but it requires abusing the official API a bit.</p>



<p>The built-in <code>assert()</code> method is implemented atop&nbsp;<code>_isDebugAssertConfiguration()</code> just the same as <code>inDebugBuilds()</code> is, above. &nbsp;However, because it&#8217;s part of the Swift standard library, <em>you</em> don&#8217;t have to be concerned about its implementation, its use of private / undocumented language, compiler, or library features &#8211; that&#8217;s up to the Swift compiler team. &nbsp;Most importantly, use of it in your code doesn&#8217;t emit an unreachable code warning.</p>



<p>So we can do 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">func</span><span style="color: #000000"> </span><span style="color: #795E26">inDebugBuilds</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">: () -&gt; </span><span style="color: #267F99">Void</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">assert</span><span style="color: #000000">({ </span><span style="color: #795E26">code</span><span style="color: #000000">(); </span><span style="color: #AF00DB">return</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></code></pre></div>



<p>Ugly and obtuse, but functional and reasonably expected to work in all future versions of Swift.</p>



<p>You can similarly create a variant for release-build-only code, though it&#8217;s even hackier:</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">inReleaseBuilds</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">: () -&gt; </span><span style="color: #267F99">Void</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"> skip: </span><span style="color: #267F99">Bool</span><span style="color: #000000"> = </span><span style="color: #0000FF">false</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">assert</span><span style="color: #000000">({ skip = </span><span style="color: #0000FF">true</span><span style="color: #000000">; </span><span style="color: #AF00DB">return</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: #AF00DB">if</span><span style="color: #000000"> !skip {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">code</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>Icky, but it works. &nbsp;On the upside, you only have to define this ugliness in one place in your module, and then you can try to forget about its implementation.&nbsp;😝</p>



<p>One caveat is that I don&#8217;t know if the above approach will properly strip out the unreachable code from your built binaries.</p>



<p>Another caveat with these is that since you&#8217;re invoking a function, you can&#8217;t do a natural <code>if … else …</code> pattern &#8211; instead you need explicit, distinct <code>inDebugBuilds</code> &amp; <code>inReleaseBuilds</code> blocks. &nbsp;So it can&#8217;t be completely like the vanilla <code>#if DEBUG … #else …</code> that C-family languages have had since before the dinosaurs. &nbsp;You could create versions that take two closures as parameters &#8211; one for the affirmative case, one for the other… the trade-off is that your invocations are then a little more verbose and obviously function-cally, 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">func</span><span style="color: #000000"> </span><span style="color: #795E26">forBuildStyle</span><span style="color: #000000">(</span><span style="color: #795E26">debug</span><span style="color: #000000"> </span><span style="color: #001080">debugCode</span><span style="color: #000000">: () -&gt; </span><span style="color: #267F99">Void</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                    </span><span style="color: #795E26">release</span><span style="color: #000000"> </span><span style="color: #001080">releaseCode</span><span style="color: #000000">: () -&gt; </span><span style="color: #267F99">Void</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"> debugBuild: </span><span style="color: #267F99">Bool</span><span style="color: #000000"> = </span><span style="color: #0000FF">false</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">assert</span><span style="color: #000000">({ debugBuild = </span><span style="color: #0000FF">true</span><span style="color: #000000">; </span><span style="color: #AF00DB">return</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: #AF00DB">if</span><span style="color: #000000"> debugBuild {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">debugCode</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">releaseCode</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">forBuildStyle</span><span style="color: #000000">(</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">debug</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;I&#39;m built for debuggin&#39;!&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 style="color: #795E26">release</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;I&#39;m built for the wild!&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></code></pre></div>



<p>Up to your personal preferences as to which API you adopt, and which implementation you choose (streamlined but private-Swift-bits-dependent with bonus unnecessary compiler warnings, or official-APIs-only but hacky).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/if-debug-in-swift/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3853</post-id>	</item>
		<item>
		<title>Swift&#8217;s String.write(toFile:…) can&#8217;t handle tildes</title>
		<link>https://wadetregaskis.com/swifts-string-writetofile-cant-handle-tildes/</link>
					<comments>https://wadetregaskis.com/swifts-string-writetofile-cant-handle-tildes/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 10 Dec 2016 06:57:13 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=3804</guid>

					<description><![CDATA[Result? &#160;Explode: Error Domain=NSCocoaErrorDomain Code=4 "The folder “sigh.txt” doesn’t exist." UserInfo={NSFilePath=~/Desktop/sigh.txt, NSUserStringVariant=Folder, NSUnderlyingError=0x1018110b0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}} And since there&#8217;s no documentation on that write() method, and this is obviously a perfectly reasonable request that can&#8217;t sanely yield that bizarre error message, you could be forgiven for having no idea how&#8230; <a class="read-more-link" href="https://wadetregaskis.com/swifts-string-writetofile-cant-handle-tildes/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<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"> path = </span><span style="color: #A31515">&quot;~/Desktop/sigh.txt&quot;</span></span>
<span class="line"><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #A31515">&quot;WTF?&quot;</span><span style="color: #000000">.</span><span style="color: #795E26">write</span><span style="color: #000000">(</span><span style="color: #795E26">toFile</span><span style="color: #000000">: path, </span><span style="color: #795E26">atomically</span><span style="color: #000000">: </span><span style="color: #0000FF">true</span><span style="color: #000000">, </span><span style="color: #795E26">encoding</span><span style="color: #000000">: .</span><span style="color: #001080">utf8</span><span style="color: #000000">)</span></span></code></pre></div>



<p>Result? &nbsp;Explode:</p>



<pre class="wp-block-preformatted">Error Domain=NSCocoaErrorDomain Code=4 "The folder “sigh.txt” doesn’t exist."
UserInfo={NSFilePath=~/Desktop/sigh.txt,
          NSUserStringVariant=Folder,
          NSUnderlyingError=0x1018110b0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}</pre>



<p>And since there&#8217;s no documentation on that <code>write()</code> method, and this is obviously a perfectly reasonable request that can&#8217;t sanely yield that bizarre error message, you could be forgiven for having no idea how to fix this.</p>



<p>Long story short, in macOS Sierra at least that <code>write()</code> method <em>happens</em> to be implemented as <code>NSString</code>&#8216;s <code>writeToFile:atomically:encoding:error:</code> method.  If you look at the documentation for that method, it states:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>path:  The file to which to write the receiver.  If path contains a tilde (~) character, you must expand it with <code>stringByExpandingTildeInPath</code> before invoking this method.</p>
</blockquote>



<p>WTF it can&#8217;t actually say that in the Swift method&#8217;s documentation (or indeed why it has none at all), or why the exception thrown can&#8217;t at least give the&nbsp;<em>real</em> reason, or of course WTF it can&#8217;t just do the right thing and handle tildes in the path… &nbsp;I have no idea. &nbsp;This smells like a combination of laziness, snafu, and poor judgement in carrying over bad behaviours and design flaws from Objective-C.</p>



<p>So instead you have to do:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #A31515">&quot;WTF?&quot;</span><span style="color: #000000">.</span><span style="color: #795E26">write</span><span style="color: #000000">(</span><span style="color: #795E26">toFile</span><span style="color: #000000">: (path as NSString).</span><span style="color: #001080">expandingTildeInPath</span><span style="color: #000000">, </span><span style="color: #795E26">atomically</span><span style="color: #000000">: </span><span style="color: #0000FF">true</span><span style="color: #000000">, </span><span style="color: #795E26">encoding</span><span style="color: #000000">: .</span><span style="color: #001080">utf8</span><span style="color: #000000">)</span></span></code></pre></div>



<p>Sigh.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/swifts-string-writetofile-cant-handle-tildes/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3804</post-id>	</item>
		<item>
		<title>Building John The Ripper Jumbo for macOS Sierra</title>
		<link>https://wadetregaskis.com/building-john-the-ripper-jumbo-for-macos-sierra/</link>
					<comments>https://wadetregaskis.com/building-john-the-ripper-jumbo-for-macos-sierra/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 12 Nov 2016 02:42:40 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[John The Ripper]]></category>
		<category><![CDATA[OpenSSL]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">https://blog.wadetregaskis.com/?p=3740</guid>

					<description><![CDATA[It&#8217;s quickly apparent that John The Ripper Jumbo&#160;doesn&#8217;t build out of the box on macOS, and probably hasn&#8217;t for a long time, due to its complaint about missing OpenSSL headers. This guide was almost helpful, except it&#8217;s out of date &#8211; e.g. the Makefile.in patch it provides no longer applies cleanly &#8211; and simply doesn&#8217;t&#8230; <a class="read-more-link" href="https://wadetregaskis.com/building-john-the-ripper-jumbo-for-macos-sierra/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>It&#8217;s quickly apparent that <a href="https://github.com/openwall/john/tree/bleeding-jumbo" data-wpel-link="external" target="_blank" rel="external noopener">John The Ripper Jumbo</a>&nbsp;doesn&#8217;t build out of the box on macOS, and probably hasn&#8217;t for a long time, due to its complaint about missing OpenSSL headers.</p>



<p><a href="https://web.archive.org/web/20231219225305/https://3583bytesready.net/2016/02/17/building-john-the-ripper-1-8-0-jumbo-on-mac-os-10-11-el-capitan/" data-wpel-link="external" target="_blank" rel="external noopener">This guide</a> was almost helpful, except it&#8217;s out of date &#8211; e.g. the Makefile.in patch it provides no longer applies cleanly &#8211; and simply doesn&#8217;t work &#8211; once you get the John The Ripper configure script to see the OpenSSL devel headers, it then just complains that it can&#8217;t find a valid libssl anyway.</p>



<p>Even the <a href="https://openwall.com/john/pro/macosx/" data-wpel-link="external" target="_blank" rel="external noopener">Pro version of John The Ripper</a>, which isn&#8217;t cheap, doesn&#8217;t look like a good option since its web page has the hallmarks of something that hasn&#8217;t been updated in many, many years. &nbsp;e.g. talk about support for Mac OS X 10.7 Lion being&nbsp;<em>planned</em>.</p>



<p>And although it&nbsp;<em>appears</em> to support using CommonCrypto instead of the now deprecated OpenSSL, that doesn&#8217;t work &#8211; even when configured that way it still compiles in code that requires OpenSSL, for SHA1. &nbsp;Sigh.</p>



<p>Trying to get it to use a fresh build of OpenSSL (1.1.0) also seems an intractable failure &#8211; OpenSSL 1.1.0 out of the box produces libraries which don&#8217;t contain SSL_library_init, which is of course necessary for any OpenSSL user and foils&nbsp;any attempt to use the built libraries by way of&nbsp;missing symbol errors in the link phase.</p>



<p>And OpenSSL 0.9.8zh&#8217;s build system is just screwy. &nbsp;By default it builds only 32-bit and only static libraries (no matter how hard you tell it to build shared ones). &nbsp;You have to bypass its first layer of configery and do it &#8216;manually&#8217;, like so:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><code>./configure --prefix=&lt;install location> darwin64-x86_64-cc -no-shared enable-camellia</code></p>
</blockquote>



<p>You can then configure JohnTheRipper to point to that version of OpenSSL, like so:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><code>./configure CPPFLAGS='-I &lt;OpenSSL install location>/include' LDFLAGS='-L &lt;OpenSSL install location>/lib' OPENSSL_LIBS="-lcrypto"</code></p>
</blockquote>



<p>Now it&#8217;ll finally get past the OpenSSL issues, and build successfully.</p>



<p><strong>Note:</strong> &nbsp;<em>don&#8217;t</em> use the Makefile.in patch provided in the aforelinked guide. &nbsp;That actually&nbsp;<em>breaks</em> the build now, even if you properly apply it manually.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/building-john-the-ripper-jumbo-for-macos-sierra/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3740</post-id>	</item>
		<item>
		<title>Do Nikon teleconverters work on the Sigma 105 macro?</title>
		<link>https://wadetregaskis.com/do-nikon-teleconverters-work-on-the-sigma-105-macro/</link>
					<comments>https://wadetregaskis.com/do-nikon-teleconverters-work-on-the-sigma-105-macro/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 29 Oct 2014 12:50:20 +0000</pubDate>
				<category><![CDATA[Photography]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Tested]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">http://blog.wadetregaskis.com/?p=3004</guid>

					<description><![CDATA[In a word:  no.  And not just &#8220;they&#8217;re unsupported&#8221;, or that they have optical issues &#8211; they physically will not connect.  They are deliberately keyed to be incompatible.  Nikon teleconverters have a protrusion on their lens mount, which prevents any &#8216;standard&#8217; Nikon-mount lens from attaching, unless that lens is missing a particular obstruction on the rear&#8230; <a class="read-more-link" href="https://wadetregaskis.com/do-nikon-teleconverters-work-on-the-sigma-105-macro/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[<p>In a word:  no.  And not just &#8220;they&#8217;re unsupported&#8221;, or that they have optical issues &#8211; they physically will not connect.  They are deliberately keyed to be incompatible.  Nikon teleconverters have a protrusion on their lens mount, which prevents any &#8216;standard&#8217; Nikon-mount lens from attaching, unless that lens is <em>missing</em> a particular obstruction on the rear mount.</p>
<p>This is not unique to the Sigma 105, by any means.  Nikon&#8217;s own 70-300, for example, has the exact same obstruction and also will not mount to any Nikon teleconverter (or at least, not any of the current models, of the 1.4x, 1.7x or 2x).</p>
<p>This is very disappointing, and an odd decision by Sigma &#8211; it was they, after all, who deliberately included the obstruction on the rear mount.  The teleconverters would mount just fine otherwise &#8211; there is just enough clearance to the rear element.</p>
<p>In contrast, Nikon&#8217;s competing 105 macro <em>doesn&#8217;t</em> have the obstruction on the mount, apparently, as it&#8217;s listed by Nikon as officially supporting use with Nikon teleconverters.  [I have not personally verified this]</p>
<p>I have heard, though haven&#8217;t verified first-hand, that the Sigma teleconverters do not have the same protrusion, and so will happily mount to any lens on which they fit (i.e. where their front element doesn&#8217;t collide with the rear element of the lens).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/do-nikon-teleconverters-work-on-the-sigma-105-macro/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3004</post-id>	</item>
		<item>
		<title>iCloud Photo Streams have limits</title>
		<link>https://wadetregaskis.com/icloud-photo-streams-have-limits/</link>
					<comments>https://wadetregaskis.com/icloud-photo-streams-have-limits/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 25 Sep 2013 02:01:14 +0000</pubDate>
				<category><![CDATA[Photography]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[lies]]></category>
		<category><![CDATA[Undocumented]]></category>
		<guid isPermaLink="false">http://blog.wadetregaskis.com/?p=2781</guid>

					<description><![CDATA[Officially, Apple claims that you can store your last 30 days of photos in iCloud via Photo Streams.  This is true only if you don&#8217;t have very many.  If you actually take a lot of photos, iCloud boots your arse out and doesn&#8217;t let you use Photo Streams anymore.  At all. 24/09/13 6:54:08.457 PM PhotoStreamAgent[305]:&#8230; <a class="read-more-link" href="https://wadetregaskis.com/icloud-photo-streams-have-limits/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[<p>Officially, Apple claims that you can store your last 30 days of photos in iCloud via Photo Streams.  This is true only if you don&#8217;t have very many.  If you actually take a lot of photos, iCloud boots your arse out and doesn&#8217;t let you use Photo Streams anymore.  At all.</p>
<pre style="padding-left: 30px;">24/09/13 6:54:08.457 PM PhotoStreamAgent[305]: Transaction forbidden. Code: 403.
24/09/13 6:54:08.457 PM PhotoStreamAgent[305]: MSPublishStreamsProtocol - XXXXX Put connection has failed. Error: __NSCFError:
Domain : streamsProtocolCoreErrorDomain
Code : 3
Desc : The Photo Stream server has rejected the user's request.
UserInfo: {
 NSLocalizedDescription = "The Photo Stream server has rejected the user's request.";
}
24/09/13 6:54:08.457 PM PhotoStreamAgent[305]: MSPublisher - XXXXX Received a quota error.
24/09/13 6:54:08.457 PM PhotoStreamAgent[305]: MSPublisher - XXXXX Quota error code: 4033</pre>
<p>Thanks iCloud. Screw you too.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/icloud-photo-streams-have-limits/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2781</post-id>	</item>
	</channel>
</rss>
