Archive for October 30, 2025

Thursday, October 30, 2025

Swift 6.2: Subprocess

Holly Borla:

Swift 6.2 introduces a new Subprocess package that offers a streamlined, concurrency‑friendly API for launching and managing external processes.

SF-0007:

The existing Foundation API for spawning a process, NSTask, originated in Objective-C. It was subsequently renamed to Process in Swift. As the language has continued to evolve, Process has not kept up. It lacks support for async/await, makes extensive use of completion handlers, and uses Objective-C exceptions to indicate developer error. This proposal introduces a new package called Subprocess, which addresses the ergonomic shortcomings of Process and enhances the experience of using Swift for scripting and other areas such as server-side development.

It’s currently available as a package, rather than being built into the OS, so the API may not be fully stable, but you can use it on older OS versions.

One of the issues with NSTask/Process is that the simple API works for small amounts of input/output but (unbeknownst to many, even some experts) hangs once you exceed the OS’s buffer size. There are ways around this, but they are awkward (though you can hide them in a helper class). As Christian Tietze (Mastodon) writes:

Helge Heß pointed out that naive usage of Pipe in child Processes can break your program if you pipe too much data.

[…]

If the data is larger than the pipe buffer, you need to drain the corresponding FileHandle with repeated read calls. (Or provide data larger than 64KiB with repeated write calls, respectively.)

If you try to send/receive the whole buffer in one go, from a user’s perspective, your program will freeze, and the read call never return. As a CLI app, it’ll never terminate.

Subprocess seems to be designed to transparently handle this. You can specify the output size limit (and the encoding, if you want a string output), and it will collect the pieces of output as they arrive.

I’ve been using Swift to write scripts, and scripts often need to run shell commands. Unfortunately, Subprocess is currently cumbersome to use for scripting, as Jacob Bartlett writes:

These simple scripts have ludicrous amounts of overhead for what are trivial one-liner operations in the bash shell. Bear with me, because later we’ll look at where Swift subprocess can actually work well, in a more complex workflow.

The additional overhead of requiring a full SwiftPM project, compared to a Bash script, makes it incredibly cumbersome for simple workloads. Also, it’s still not an actual script, so it’ll always require a compilation (and potentially dependency resolution) overhead whenever your code changes.

On the other hand, the syntax, type safety, and composability of Swift code work pretty nicely when you have complex automation workflows to orchestrate, build, and run on demand.

Previously:

Update (2025-12-01): Sarah Reichelt:

There are immediate benefits to using Subprocess. I didn't have to specify the full path to the whoami command, and I didn't have to set up a pipe and a file handle to see the result.

[…]

That's when I found out that arguments must be an array of Arguments, not Strings.

It’s not an Array of Arguments; Arguments itself is an array-like type. The comment says it’s “a collection,” but it does not actually seem to be a Collection. Anyway, this is annoying because I’m often constructing arguments lists in code rather than using literals. I wonder if there wasn’t a better way to provide the executablePathOverride functionality than by making all the users who don’t need that use a whole new type.

This worked, but I was still getting the complete output at the end, and not seeing each line as it arrived. To help in my research, I selected Build Documentation from the Product menu which gave me the docs for Subprocess in Xcode's Developer Documentation. There are a lot of custom types, but only one function - run - which has 14 different versions. Seven of these variants expect an Executable, which is what I've been using when I specify a .name. The others expect a Configuration which is a way of combining an executable and its arguments plus other configuration details, into a single object.

I also discovered that the versions that streamed data had a preferredBufferSize setting.

[…]

I set the preferredBufferSize to 32 and watched the lines come in one by one. It would be great if there was an option to buffer until a line feed, but this is workable.

NeoFinder 9.0

Norbert Doerner:

New Cataloging engine with more speed and reliability

[…]

Ability to move files to new folders inside a Catalog

Text Replacements for the XMP Editor for faster annotations

[…]

Image Capture to import photos and videos from connected cameras, including adding XMP presets, and store them in a date based folder structure [guide]

[…]

QuickPerson panel to add persons with just a single click

QuickTags panel to add keywords with just a single click

[…]

Multi Renamer has ten more variables to be placed in the new names

NeoFinder (formerly CDFinder) is now 30 years old. The new version is $39.99 to purchase or $25.99 to upgrade.

Previously:

Update (2025-12-10): Norbert Doerner:

On a cold Winter evening, December 5, 1995, the first lines of code for a new disk cataloger tool named CDFinder were written in a little village not far from Aschaffenburg, Germany.

Norbert Doerner:

So we uncovered a very old project plan of CDFinder 1.0, from 1996, with details on how the programming of version 1.0 went!

(Cool thing that LibreOffice is able to open the old Apple ClarisWorks files that we thought were lost!)