❌

Normal view

Getting a File Path From a Swift URL

20 October 2026 at 20:24

Seth Willits (2024, bug):

There’s a deprecation warning in Xcode for URL.path that reads:

WARNING: path will be deprecated in a future version of macOS: Use path(percentEncoded:) instead

The problem is something like this:

Bundle.main.url(forAuxiliaryExecutable: "tool")!.path

… which returns the expected file path (ex: /Applications/MyApp.app/Contents/tool)

would be quickly rewritten as suggested to:

Bundle.main.url(forAuxiliaryExecutable: "tool")!.path(percentEncoded: false)

… which simply returns tool, because the URL returned is relative to a base URL.

[…]

I’ve always found it not-completely-obvious to newcomers how to get a standard file path string from a file URL and this only makes it more confusing.

There are at least three issues here:

  1. url.path returns an absolute path, but url.path(percentEncoded: false) returned a relative path as of his writing. I tested this on macOS 26, and now it returns an absolute path. The documentation now says, “This function will resolve against the base URL.” It seems the behavior of the method changed without it being documented when, so be careful if you’re deploying to a mix of OS versions.

  2. percentEncoded has a default value of true, whereas path did not return an encoded string. In other words, url.path() and url.path are completely different. So this is another case where Apple updated the API but flipped the default—a good way to introduce bugs.

  3. It’s not obvious how to get the full file system path out of a URL. With NSURL, url.path was natural and did the right thing. Now, the URL equivalent is deprecated.

    The natural replacement, url.path(), is wrong. So is Xcode’s recommendation of url.(percentEncoded: false).

    url.absoluteURL.path(percentEncoded: false) is correct but verbose.

    url.absoluteURL.standardizedFileURL.path(percentEncoded: false) is possibly better but even more verbose.

    FilePath(url)?.string uses a newer API but gives you an optional, even though for a file URL it should never fail.

    This seems like such basic functionality, but there’s no API that’s the obvious right choice, concise, and ergonomic.

Previously:

The Unreasonable Effectiveness of Constructive Data Modeling

7 October 2026 at 19:47

Alexis King (tweet):

The type systems provided by mainstream languages have grown vastly more sophisticated, but how much value does that complexity actually provide programmers in practice? I would like to make the case that it is somewhat less than one might think. While at times convenient, invariants can be captured in the type system using a small set of simple tools applied in principled fashion, and while fancy type systems may be convenient, they have misled many programmers by obscuring what is possible to model without built-in type system support. This talk aims to provide a simple shift in perspective, plus a set of concrete design principles, to make modeling invariants in types feel accessible to the working programmer without fancy language support.

“Type system as obligation tracker” is a catchy phrase, like her previous “parse, don’t validate” (Hacker News).

See also: Matt Diephouse.

Backporting SwiftUI APIs

30 September 2026 at 21:45

Dave DeLong:

I can improve the process of removing backwards compatibility shims by using conditional compilation to remind me when they’re no longer necessary!

[…]

This does mean that the first time we change our deployment target, we’ll get a bunch of compilation errors. But given the nature of how Backport is implemented, this should be a relatively quick process to move past. (And of course, you’re welcome to use #warning instead of #error).

Shaps Benkau:

SwiftUIBackports mirrors Apple’s SwiftUI API names, shapes, behavior, documentation, and availability where practical. This keeps app code familiar today, and easy to migrate away from once your deployment target reaches the native API.

Backports are discovered through .backport, the Backport namespace, or backport-prefixed environment values.

Majid Jabrayilov:

Usually, we don’t see many new APIs in intermediate releases like 27.1, but this time is different. Apple introduced several new iPhone Duo-related APIs in the 27.1 release. This week, we will learn how to use all of these new APIs without bumping your project’s target version.

[…]

Here we introduced the toolbarVerticalBehavior function on the Backport type that handles availability checks and calls the new APIs if available or returns a plain view when it is not available.

Previously:

SpamSieve 3.3.2

18 September 2026 at 21:37

SpamSieve 3.3.2 is an update of my Mac e-mail spam filter that improves remote training, can export more rule information, and improves uninstallation, help, and more. The most significant news is that Microsoft has changed plans again. They will no longer be preventing users from continuing to use Legacy Outlook and in fact will keep it available for download as part of the combined Outlook app. This gives more time to adapt to their change in plans for AppleScript. Exchange (EWS) support remains an issue. Microsoft is still planning to cut off Legacy Outlook users, and Apple has yet to announce when Mail will support Graph. So far, I’ve not heard that it’s enabled in the macOS 27.2 beta.

We’re participating in a seasonal festival of artisanal software called SummerFest. For a limited time, you can get a 25% discount on new purchases of SpamSieve, EagleFiler, and other top Mac apps from our indie developer friends.

Some interesting engineering issues were:

  • Updating NSWorkspace APIs introduced a problem because with the old launchApplication(at:options:), the .withErrorPresentation option is off by default, whereas with the newer openApplication(at:configuration:completionHandler:), the promptsUserIfNeeded option is on by default.

  • Previously, Core Data would strip leading BOMs from NSStrings when saving to a SQLite database. With macOS 27, this happens for Swift Strings, too.

  • I have lots of unit tests that check for memory leaks. These caught a change where addPersistentStore(type:configuration:at:options:) now seems to return an autoreleased store. Without adding a pool, it looked like there was a leak.

  • Also, the tests were finding lots of “leaks” because KVO was retaining NSTabView, which retains a controller. Previously, XCTestCase.waitForExpectations(timeout:) would spin the run loop, and everything would get cleaned up. With Xcode 27 and macOS 27, I need to manually spin the run loop before waiting for the expectations.

  • During a software update, macOS may decide to reload the Dock icon. The application package no longer exists because it’s been replaced with a new version. This wasn’t a problem before because SpamSieve had been drawing to an image in memory and setting that as the Dock icon. Now, with the Liquid Glass–imposed removal of custom Dock icons, macOS tries to load the icon from disk, and rather than handling errors gracefully it will crash. I fixed this by setting an image with a memory representation before starting a software update.

  • SpamSieve 3.3.1 introduced a crash affecting a small but significant number of users when doing a Core Data fetch of a large number of objects. The private [_PFManagedObjectReferenceQueue _processReferenceQueue:] method was seemingly releasing an object that had already been released. What’s odd is that, as far as I can tell, none of the related app code had changed since the previous version, yet suddenly crashes started happening all the way from macOS 12 through macOS 27. If I had to guess, this was triggered by upgrading from Xcode 26.2 to Xcode 26.6. Maybe Swift’s ARC optimizations are doing something subtly different?

    The crash was triggered by an autorelease pool that I had inserted to promptly clean up any temporary objects after a fetch. (The actual fetched objects were returned and used outside of the pool.) Interestingly, both Gemini and ChatGPT were convinced that this is a well-known exception to standard memory management rules. They said it’s never safe to use managed objects outside of a pool they were fetched in because they depend on internal Core Data objects that the pool will have released. I was not able to find any basis for this “belief,” but it was easy enough to test. As expected, removing the pools (during the beta cycle) just made the same crashes happen slightly later.

    I think there’s probably something wrong in Core Data’s internal bookkeeping, but this does not seem to be a popular crash (and I’ve never seen it on any of my Macs), so my code must be doing something uncommon to trigger it. My current best guess is that it was related to calling refreshAllObjects() after saving. (I bet there’s some fancy bookkeeping to make that efficient.) I was doing this throughout the app to break retain cycles so that unused objects could be deallocated. But it wasn’t necessary in this particular case because the entity being fetched has no relationships. So far, removing the refresh seems to have stopped the crashes, but I won’t know for sure until more people have been using this version for a while.

Previously:

Xcode’s New .xcproj Files Are JSON

17 September 2026 at 19:32

Apple:

Xcode now supports a JSON-based project format (.xcproj) that’s more readable, merge-friendly, and easier for coding agents to edit. Enable it in the file inspector. Projects using .xcproj also open in earlier versions of Xcode 27. Learn more in Updating your Xcode project configuration file format.

Fatbobman:

[The] new project.xcproj caught my eye. What it replaces is project.pbxproj, the build graph inside the .xcodeproj bundle, not the bundle itself.

[…]

In the age of agents, the drawbacks of pbxproj are amplified further. A great many IDs carry both object identity and cross-references, so an agent modifying a project usually has to locate the right object in a flat table, then update several mutually referencing places in step, all while trying to keep the serialized output faithful to Xcode’s conventions.

[…]

From a flat ID table to a tree that mirrors the UI: instead of dumping everything into objects, it unfolds into structures like files / targets / build-settings, so diffs look closer to the operations you actually performed in the interface;

The direction of relationships is reversed: a file can declare which targets it belongs to via target-membership, so adding a file no longer means editing several mutually referencing places at once;

Simon B. Støvring:

Developers have been screaming for a friendlier Xcode project format for years. I’m one of them.

Tony Arnold:

I’ve been waiting for something like this for most of my career! 🎉

Rob Napier:

It does feel like another case of “we’ve quickly done a thing to help AI that we’ve been unwilling to do for years to help humans.” I’m seeing that happen across the industry.

It is very welcome and appreciated, and lots of thanks to the teams involved. It’s also frustrating that it took “it can help AI” to make it happen.

Apple:

xcode-project-format is a Swift library for reading, writing, and manipulating Xcode’s JSON-based project.xcproj format. It models the format as a rich set of value types nested under a single namespace, XCSchema, so consumers get precise, typed access to a project’s structure instead of hand-parsing JSON.

The goal is to give the ecosystem a shared, accurate model of the Xcode project format — so tools like generators, linters, verifiers, and validators don’t each have to reverse-engineer the format from scratch.

Helge Heß:

Didn’t most people switch to local SPM projects anyways? Those are the new xcproj that have to be replaced with sth sensible 🙃

Fatbobman:

What the community has actually been waiting years for is a project manifest along the lines of Package.swift: something written by people, read by people, and reviewed by people, rather than a more modern encoding of the IDE’s internal database.

Previously:

Update (2026-09-23): John Gruber:

The old .pbxproj project format was not XML; it was an old NeXT-style “ASCII” property list — a file format that is surprisingly JSON-like considering that it predates not only JSON, but JavaScript (JSON = JavaScript Object Notation).

❌