Learning Swift Development for macOS by Building a Website Blocker
I loved Focus App. It blocked websites and apps on a schedule. But, years ago it started glitching out: sucking up tons of ram and freezing my computer. They didn’t fix the bug and I abandoned using it and instead switched to a host-based blocking system which has served me well.
However, there are some issues with the host-based approach:I can’t block specific URLs, only hosts (focus app couldn’t do this either) I can’t set a schedule I can’t block apps If I remove a host it will not automatically get blocked unless I sleep and wake the computer Sleepwatcher (cli tool) is dead and requires some manual set up to get working.
My goal is to layer on top of the existing host-based system that has been working great and add another layer of focus tooling:CLI-first tool Allow configuration to be easily set using a JSON file Allow different blocking configuration to be scheduled Replace sleepwatcher by configuring script execution on wake Add a ‘first wake of the day’ trigger that I can tie into clean browsers and todoist scheduler Allow both hosts and partial match urls to be blocked ‘Partial match’ means (a) anchors are excluded and (b) the configured block url must only be a subset of the url on the browser in order to be blocked. This will enable things like blocking news or shopping search on google. Support blocking urls in google chrome and safari No UI, maybe build a simple REST API that could be tied into my beloved Raycast Run CLI tool as privileged (in order to mutate /etc/hosts)
With a clear goal in mind for this learning project, I was able to get started and build this out. Here are the two repos with the resulting code:hyper-focus CLI source code hyper-focus GUI via Raycast extension
This is simple as long as you do bind to a local IP: localhost, 127.0.0.1, etc. If you bind to your router’s IP address you’ll run into all sorts of permissioning issues:The default permissioning is different depending on what macos version you are on. Here’s an example of how to check an application’s default permissioning You cannot change your entitlements/permissions if you are just building a simple binary or cli app. You need an app with a Info.plist to set the proper security config. This is because of new security stuff that apple has introduced. This means you need to use xcode to setup and build your application. I couldn’t find any good examples of an app that is built without using XCode. The alternative to this is using another layer of indirection, like tuist. This is bringing back memories of all of the stuff I hated about desktop application development. Don’t bind to the device IP (i.e. the wifi- or ethernet-assigned address) unless you need to. Bind to localhost so the server is only accessible on the device. Swift server package options https://criollo.io https://github.com/httpswift/swifter https://github.com/Building42/Telegraph https://github.com/envoy/Ambassador Packaging
Not using a Package.swift for anything even slightly complex will bring a world of pain:The VS Code tooling doesn’t work as well (no error highlights and LSP stuff) You can’t use a package manager and therefore can’t easily pull in community packages Anything that uses swift build doesn’t work
You’ll want to use a Package.swift in your project. Generating a Package.swift is pretty easy:swift package init --type executable
When running swift build I ran into:no such module 'PackageDescription
This post describes the issue and the following command fixes it for me:sudo xcode-select --reset
If you run into issues with compilation errors due to some features not being available on older macos versions, you’ll need to add a platform requirement to your Package.swift:platforms: [ .macOS(.v13) ],
Here’s an example Package.swift for the CLI tool.Cleaning All Cache
I ran into a very weird build error:❯ swift run Building for debugging... Build complete! (0.25s) dyld: Symbol not found: (_$s10Foundation11JSONDecoderC6decode_4fromxxm_AA4DataVtKSeRzlFTj) Referenced from: '/Users/mike/Projects/focus-app/.build/x86_64-apple-macosx/debug/focus-app' Expected in: '/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation'  21481 abort swift run
Even after resetting the project to a state where I knew it compiled, it still errored out. After walking away for a while, I found this post and tried updating the min macos version. It magically fixed the issue.
Here’s what I used to clear all build caches:rm -Rf .build/ rm Package.resolved rm -Rf ~/Library/Developer/Xcode/DerivedData rm -Rf /Users/mike/Library/Caches/org.swift.swiftpm Open Questions Is there a way to open a repl with your application’s code imported? It was nice that a compiled language had a recent repl, but ideally, I want to open a repl and be able to import/use my applications code. How is the debugger? I just did caveman debugging for this project and didn’t bother understanding the GUI debug tooling. It’s unclear how good the package ecosystem is. It seems better than my Cocoa days, but there weren’t that many options and the package activity seems pretty dead. It doesn’t seem like you can build a .app without an xcode project. This is annoying, especially if you are building a small tool and don’t want to learn and understand the xcode toolchain (it still seems terrible). I wonder if I’m missing something here and if there’s some good tooling to support a CLI-based application build? I was surprised at how many errors were not reported. If you’ve subscribed an object as an observer to a notification center, the object was GC’d, that should give you an error. It seems like there were a good number of silent failures which made it harder to discover unexpected failures, especially to someone who is not a desktop developer. I wonder if there’s some env flags that change this behavior. I never understood/learned exactly what the @ does in Swift. It looks like a JS/Python decorator, but it’s unclear if all of the annotations are owned by Swift or if developers can write their own. Where is the documentation for all of the magic variables? i.e. error in a catch block? Open Source https://github.com/Ranchero-Software/NetNewsWire https://github.com/rxhanson/Rectangle Has automated some of the release process https://github.com/exelban/stats https://github.com/kean/PulsePro https://github.com/piemonte/Player https://github.com/cirruslabs/tart https://github.com/signalapp/Signal-iOS https://github.com/onevcat/Rainbow https://github.com/Sequel-Ace/Sequel-Ace https://github.com/HedvigInsurance/ugglan https://github.com/lvillani/chai https://github.com/halo/LinkLiar Thoughts on Swift
Swift is a really nice language. I like how it is strongly typed, but the typing system is good at inferring types when it can, so you don’t have to specify that many types. The type inference seems very good—better than TypeScript, Sorbet, and python from what I can tell.
I don’t like how there are not any imports, and how anything marked as public can clutter the global namespace. I hate this about ruby, and it’s something I think python gets very right. I wish there would be explicit imports and any package-level functions would be forced to be called with their package name. I can understand how this would get very messy with the objc stuff, but that could have been special-cased in some way.
Some of the objc interface stuff is strange, but I think the language designers did a very good job of dealing with it in a simple way.
The tooling isn’t bad but there are some strange gaps in the stdlib, largely because of the legacy cocoa infrastructure you can leverage. I found this annoying: there’s not a simple logger, there’s no built-in yaml parser, etc. The Cocoa apis have a lot of legacy decisions to deal with and they are generally a pain to use. I wish the stdlib was more expansive and designed without thinking about the legacy APIs too much.
The package manager requires you to build your application in a specific way, which is annoying, but if you follow the golden path things work in a pretty clean way. It’s nice that there is an official package manager that Apple is committed to maintaining.