SpamSieve 3.3.2
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
NSWorkspaceAPIs introduced a problem because with the oldlaunchApplication(at:options:), the.withErrorPresentationoption is off by default, whereas with the neweropenApplication(at:configuration:completionHandler:), thepromptsUserIfNeededoption 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 SwiftStrings, 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 callingrefreshAllObjects()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: