❌

Reading view

SpamSieve 3.3.1

SpamSieve 3.3.1 is an update of my Mac e-mail spam filter that improves the filtering accuracy and includes various optimizations, fixes, and updates for macOS 27. Some interesting engineering issues were:

  • I’m still supporting macOS 10.13, but the app can now build with the macOS 12 deployment target in preparation for Xcode 27. The way Swift handles calling deprecated APIs remains annoying. You need workarounds like protocols to suppress a warning in a particular region of code. Often, I am updating to a newer API, but I want to continue using the old one on earlier versions of macOS. Right now I am heavily testing on macOS 26, so I would like to only deploy the new code there. Sometimes the initial versions of APIs are buggy, and I don’t want to have to retest every feature on every version. But Swift doesn’t like me to gate the calls to macOS 26 and later if the new API was actually introduced in, say, macOS 11.

  • The most risky update was probably switching to the Swift API for Scanner. This is tricky because the current scan position changes from being based on UTF-16 indexes to String.Index. And in some cases my code was intentionally scanning into the middle of a combining character. I was really glad to have extensive tests here.

  • Recent versions of Tahoe introduced a Core Data bug that broke save recovery (when invalid Unicode can’t be written to the database and we have to fix it up first). When the error comes in, SpamSieve looks under the NSDetailedErrorsKey key, but the value of this key is "NSDetailedErrorsKey" while the actual error info is under the key "NSDetailedErrors".

  • Database corruption combined with a Core Data bug and two Swift behaviors were causing crashes. The root problem, I think, is that in this situation Core Data neglects to set the error pointer for -[NSManagedObjectContext countForFetchRequest:error:]. Normally, Swift would detect this and throw a generic nilError. However, this method signals errors by returning NSNotFound instead of nil. It’s annotated as swift_error(nonnull_error), which I think means that Swift doesn’t even look at the return value, only the (unset) error. So my code was receiving NSNotFound on the non-error path, treating it as a real count, and doing some math with it, which (by default in Swift) leads to a crash if there’s overflow. Now my code checks for NSNotFound and uses Int.addingReportingOverflow().

  • I reimplemented Core Data batch fetching because of two problems with the fetchBatchSize option. First, it logs spurious errors when fetching from multiple stores at once. Second, it leads to high memory use. It will load objects in batches, but it never releases the earlier batches. You can manually refault objects that you no longer need, but it will still hold onto the full object shells, which can add up. My implementation only holds onto IDs, and it can fetch the IDs using a different context than the objects to push more work to background threads.

  • I’m now using String.makeContiguousUTF8() in various places to avoid slow iteration of bridged strings.

  • There was a regression where some menu code could crash on macOS 10.13. -[NSMenu setItemArray:] is only available on macOS 10.14 and later, whereas the getter is always available. With old Objective-C, the two methods could be annotated with separate availabilities. The Objective-C property syntax doesn’t allow this, so there’s no way for the compiler to flag calls where the setter is missing.

  • The AppleScript support works much better with large data sets. The key, I think, is to never return an array to Cocoa for a scripting property/element. First, if you give it a lazy array it will sometimes try to access all the elements even if it only needs a few. And, second, AppleScript will run out of memory if the array is too large. By using indexed accessors, you can maintain laziness and only convert one object to a descriptor at a time. By caching a batch-fetching collection, you can limit the number of fetches and respond quickly when AppleScript requests individual objects.

Previously:

  •  

Swift 6.4

Swift 6.4 is now available in beta form with Xcode 27. The Swift Evolution proposals are listed here.

What’s new in Swift:

Discover the latest language advancements, including updates for everyday ergonomics, improved concurrency, and safer high-performance code. Explore workflow and language interoperability improvements and updates in embedded Swift.

Build real-time apps and services with gRPC and Swift:

Build engaging live experiences with gRPC in your Swift app and backend. gRPC is an open-source RPC framework designed for high-performance, bidirectional streaming APIs. Explore how the gRPC Swift package provides a modern, safe runtime built with Swift concurrency.

Explore numerical computing in Swift with MLX:

Bring NumPy-style computing natively to Swift with MLX Swift. Discover how to eliminate cross-language friction in your machine learning workflows by handling image processing, tensor operations, and neural network training in a single, type-safe environment. Explore the APIs that let you leverage GPU acceleration while enjoying the compiler, tooling, and debugging experience you already know.

Swift Group Lab:

Join us online for a deep dive into WWDC26 with Apple engineers and designers to ask questions, get advice, and follow the discussion about the week’s biggest Swift announcements.

Matt Massicotte:

There’s a voting system. And that means a number of questions weren’t selected. This time I copied those down and I thought it could be fun to take a shot at answering the ones that are more in my area.

Xcode 27 Beta 2 Release Notes:

The Swift dependency scanner has been optimized to avoid redundant setup work and header searches when looking up Clang modules during a single dependency-scan action, substantially improving scanning performance.

As a consequence of this change, every Clang module reachable from a single Swift dependency-scan action must have a unique module name. If two module maps visible to the same scan declare a Clang module with the same name, the scan may report an error.

[…]

System now provides Swift APIs for the C stat, lstat, fstat, and fstatat system calls. This includes a new Stat type with initializers from FilePath, FileDescriptor, or a C string; FilePath.stat() and FileDescriptor.stat() instance methods; and supporting types (FileType, FileMode, FileFlags, UserID, GroupID, DeviceID, and Inode).

Wade Tregaskis:

The final piece of the Prospective Vision for Accessors, this allows for more efficient accessors on structs, in a nutshell.

[…]

Now there’s a safer version of the existing withUnsafeTemporaryAllocation (first introduced in Swift 5.6). It works just like you’d expect. It comes in both β€œraw bytes” and typed elements versions, just like its unsafe predecessor.

[…]

The new isTriviallyIdentical(to:) method offers a guaranteed-fast way to check two objects for equality. It is essentially an optimised version of the existing equality condition provided by Equatable and the == operator. It’s useful because it guarantees O(1) time complexity, whereas == makes no formal guarantees at all (and for many types is O(N) or worse). But unlike the existing identity comparison operator === – also O(1) – it promises to do more than just tell if two objects are the exact same pointer, and it works on value types (not just reference types).

[…]

These new types generalise the existing patterns of borrowed and mutable references, that have previously existed in Swift in much more restricted forms (e.g. inout parameters are essentially MutableRef parameters, and Span & MutableSpan are array equivalents).

There’s now a built-in function for demangling Swift symbol names, in the Runtime module (that’s bundled with the Swift toolchain, alongside the stdlib etc).

Khoa Pham:

As Apple has aligned OS version numbers across platforms, Swift 6.4 takes the next step by letting you collapse repetitive availability attributes into a single anyAppleOS token.

[…]

There are situations where you need to call a deprecated API while you plan a migration, or audit a critical function for unsafe usage, without letting those concerns bleed into the rest of the project. The new @diagnose attribute handles both cases.

[…]

Some work should complete even after a task is cancelled, like flushing a file to avoid corruption. withTaskCancellationShield wraps a region where Task.isCancelled always returns false.

Antoine van der Lee:

Swift 6.4 implements SE-0493: Support async calls in defer bodies. This is one of those changes that feels obvious once you need it.

[…]

Swift 6.4 improves error handling for unstructured tasks through SE-0520: Discardable result use in Task initializers. The change helps you catch a subtle bug: creating a throwing task and then ignoring its returned task handle.

[…]

Swift 6.4 also implements SE-0530: Async Result Support.

[…]

Swift 6.4 implements SE-0518: ~Sendable for explicitly marking non-Sendable types. It allows you to communicate that a type has been audited and should not conform to Sendable.

Slava Pestov:

I thought it would be a good time to detail some of the type checker performance improvements we worked on since I shared the type checker performance roadmap last year. I’m also going to outline a couple of things we plan on looking at next.

Previously:

Update (2026-07-09): I was optimistic about improvements to the type checker making compilation faster, but I found that compiling my predominantly Swift project with Xcode 27 Beta 3 was only 0.3% faster than with Xcode 26.6. I wonder if there’s some extra setting I’m supposed to enable.

  •  

Swift 6.3

Holly Borla and Joe Heck (Hacker News):

Swift 6.3 introduces the @c attribute, which lets you expose Swift functions and enums to C code in your project. […] @c also works together with @implementation. This lets you provide a Swift implementation for a function declared in a C header[…]

[…]

Swift 6.3 introduces module selectors to specify which imported module Swift should look in for an API used in your code. If you import more than one module that provides API with the same name, module selectors let you disambiguate which API to use[…]

[…]

Provide pre-specialized implementations of a generic API for common concrete types using @specialize.

[…]

Guarantee inlining β€” a compiler optimization that expands the body of a function at the call-site β€” for direct calls to a function with @inline(always).

[…]

Expose the implementation of a function in an ABI-stable library to clients with @export(implementation). This allows the function to participate in more compiler optimizations.

The Swift Evolution proposals are listed here.

Florian Pircher:

I love that I can now name my module the same as the primary/only type of the module without making the module namespace unreachable.

Jordan Rose:

Congrats to my Swift colleagues for #finally pushing @c over the finish line! Been a long time coming.

Previously:

  •  
❌