Splitting a Xcode project with SPM

I was talking on Mastodon about splitting an Xcode project into smaller pieces. Here's an elaboration.

Background: With SwiftUI previews you want small focused Xcode schemes. The larger your scheme, the less likely the preview is to succeed. You also get faster compilation if you don't always build the whole app. You can get smaller schemes with framework build targets or with Swift Package Manager. Frameworks are a pain in the ass, SPM mostly isn't.

A note about the confusing terminology, in case you're not familiar with both Xcode and SPM: Xcode has projects, targets and schemes. SPM has products and targets. SPM products show up in the scheme selector of Xcode.

Here's how you set up my ideal Xcode app project:

Create a root directory for your project. This is where you have your .git folder or equivalent. In the root directory, create a new Xcode project, so you have root/MyApp/MyApp.xcodeproj and next to it root/MyApp/MyApp/ for the app files.

Then create a SPM package directory next to MyApp, something like root/MyAppCore/. Run swift package init --type library in it. Now open root in Finder and drag MyAppCore into MyApp in the Xcode Project Navigator sidebar (you don't need a workspace if you drop it onto the project.) After this you should have two entries under the MyApp project in Xcode: MyAppCore and MyApp. You should also see MyAppCore in the Xcode scheme selector.

After this you only have to add MyAppCore to the app build target in the Frameworks, Libraries and Embedded Content section of the project editor's General tab, and you're good to go.

With a setup like this, you can keep very little code on the app side, and put almost everything in the SPM package. You'll probably need your SwiftUI App, app delegate or scene delegate, depending on what and how you're building, on the app side, but other than that, just shove everything into the package. Every file you add to the package instead of the app is one change to the project file you avoided.

Ideally you want a wide but shallow SPM project structure with as many targets as is necessary, but structured so that they don't build up into deep dependency trees. You don't need to create a SPM product for each target; it's enough to create a projects as you need them from previews or for when you want to have Xcode compile just one part of the package.

To make imports easier in the app, you can use the MyAppCore product as an umbrella module. That means the MyAppCore target depends on every other target in the package, and then in MyAppCore/MyAppCore.swift calls @_exported import on all of them. @_exported is an underscored attribute and thus may break at some point, but it doesn't seem like a huge risk. So in Package.swift you can have this:

.target(
    name: "MyAppCore",
    dependencies: [
        "DB",
        "Networking",
        "PreferencesUI",
        /* all the other targets */
    ]
),

and then in MyAppCore.swift:

@_exported import DB
@_exported import Networking
@_exported import PreferencesUI
/* all the other targets */

Now when you do import MyAppCore on the app side, you get everything.

With a setup like you rarely need to touch the Xcode project file. It reduces the need for a tool like XcodeGen, which is priceless on a more monolithic project. If you want to avoid the project editor completely, I recommend looking into it.

A fast timestamp parser in Swift

I wrote a timestamp parser in Swift. It's called Parse3339.

It's well known that DateFormatter, the main timestamp formatter and parser Apple ships in Foundation, is not particularly fast. It's flexible and it's correct, but it takes its time. The newer ISO8601DateFormatter has similar performance.

I haven't much worried about that in the recent years. A while ago I had a problem with slow parsing but that time a caching layer solved it, as the data had a ton of identical dates. Last week I saw date parsers rear their heads in an Instruments trace again, and this time the data wasn't amenable to caching. It was happening on a background queue and it was only a few hundred milliseconds. But once you start optimizing, it's hard to let go.

In the past a common solution was using strptime(3). This time I wanted to see how far I could get with Swift. I've usually used parser combinators for parsing, but these time stamps are so regular a simpler approach felt sufficient.

Time formats have two relevant standards: ISO 8601 and RFC 3339. RFC 3339 is mostly a subset of ISO 8601, but even it allows for all kinds of irrelevant silliness. Realistically the only format I care about is a full timestamp with a T between date and time and possibly a Z for time zone. When I don't support any other variations, the resulting parser is really small, straightforward and easy to modify if your needs are different.

It's also very fast. The parser itself easily breezes through hundreds of thousands timestamps per second on my Mac. However, in most cases, you need something other than a struct with a bunch of numbers. Things slowed down once I started creating DateComponents and converting those to Dates with a Calendar. The code was still a few times faster than the platform parsers, but it was clearly leaving performance on the table. It was also using a lot of memory even though the parser itself was completely running on stack, allocating nothing. Benchmarking revealed that my code was using a similar amount of memory as DateFormatter, whereas ISO8601DateFormatter had a lighter footprint.

So back to the old Unix functions. Swift's C bridging is first-class; struct tm and timegm(3) are right there. After changing the code to use those, the whole String to Date conversion runs completely without heap allocations and it's around 6–7 times faster than when doing the detour via DateComponents.

The final result, according to Benchmark, is maybe around 15x the speed of the Foundation parsers. I'm pretty happy with it. It's available as a Swift package, there's documentation, and if you need something different or don't feel like using a package, all the parsing code is in just one file you can copy over to your project.

Git worktrees helper

I recently became an avid user of Git worktrees. However, the command line interface to them is about as great as Git command line interfaces always are. Just git worktree add is a confusing maze of options.

I wrote a shell script to help myself and then I wanted to check parameters and then I decided that was a bridge too far with shell today and went for Swift instead.

My Worktrees tool has just one command for now, because that's the pain point I ran into today. worktrees add-new-branch username/ mybranch main creates a new branch called username/mybranch and a worktree called mybranch for working on it, branching it off main. You could very well argue that it's no easier than git worktree add, but the idea is that I can define a repo-local alias for it. Almost all the branches I open are from the mainline branch of the repo I'm working in, and I want to prefix my branches with juri/. So I can add this alias, if the repo is using main:

[alias]
	wta = !worktree add-new-branch juri/ main

And now running git wta mybranch does opens me a new worktree the way I like it.

© Juri Pakaste 2023