Building Renala
18 articles on building a macOS disk analyzer in Swift and SwiftUI. From the first wireframe to the App Store. Design decisions, performance tuning, treemap rendering, and what it takes to ship a native Mac app as a solo developer.
Part 1: Foundation
Why I Built a Disk Analyzer
On picking a project with a bounded scope, interesting algorithms, and a visible result. How frustration with existing tools led to building Renala, a macOS disk space analyzer.
Hello, Xcode
I expected a compiler. I found a religion. XcodeGen, dual build systems, code signing, entitlements, and the Makefile as escape hatch.
The Treemap Problem
It's a rectangle. Inside that rectangle, there are smaller rectangles. Implementing squarified treemaps from the 1999 Bruls/van Wijk paper, and why flat colors are not enough.
Part 2: The Platform
Swift 6 and the Concurrency Cliff
The compiler told me my code was wrong. It was right. @MainActor, Sendable, actor isolation, and the bugs Swift 6 strict concurrency caught before I shipped them.
SwiftUI Is Not React
I kept looking for useState. There is no useState. The @Observable model, MVVM in practice, and the Canvas API for immediate-mode rendering inside a declarative framework.
Painting Pixels: Lambert Shading
I spent three days on pixels. Worth it. Per-pixel Lambert shading, why SwiftUI Canvas cannot do this natively, and the background Task pipeline that makes it smooth.
Part 3: Performance
The Scanner Bottleneck
44 seconds. Then 26 seconds. One syscall changed everything. FileManager's hidden costs, getattrlistbulk, and raw pointer arithmetic on macOS.
A Heartbeat for the Main Thread
If you can't measure it, you can't fix it. PerfLogWriter, MAIN_THREAD_BLOCKED detection, and the 250ms batch flush pattern that keeps the UI responsive.
From One Gigabyte to Four Hundred Megabytes
I had 5.2 million FileNode objects. I did not need them. UUID to UInt32, paths reconstructed from parent + name, and the flat NodeStore that replaced a tree.
Finding Duplicates at Scale
Hashing 300 GB of files on a laptop without killing it. The multi-phase pipeline, 6-worker TaskGroup, and the throughput gains from batching UI updates.
Testing a Visual App
526 tests. Zero of them look at a pixel. Algorithm tests, real-file scanner tests, O(n) performance gates, and why SwiftUI views are deliberately untested.
Part 4: Ship It
Eleven Languages, One Toggle
Nobody ships 11 languages on day one. I did it anyway. Live language switching, the Python translation pipeline, RTL support, and Arabic plural categories.
The App Sandbox
You cannot open a file without asking permission. Every time. Security-scoped bookmarks, NSHomeDirectory() returning the wrong path, and why read-write matters.
The App Store Gauntlet
I thought building the app was the hard part. Privacy Manifest, StoreKit 2, screenshots at 2880x1800, and the CI/CD pipeline that handles signing, notarization, and submission.
Rejected
Two rejection reasons. One was my fault. One was almost my fault. A crash on macOS Sequoia from an @MainActor closure on a background queue, and a tip jar that appeared empty during review.
Submitted
The app is in the queue. Six reviews, four versions, three weeks. What the gap between 'done' and 'shipped' actually costs.
Appendices
Where Does Nothing Run?
When you pass an empty closure to an API, who decides which thread it runs on? A cross-language comparison of JavaScript, C#, Java, Kotlin, and Swift 6.
How SwiftUI Decides What to Redraw
SwiftUI tracks which properties each view reads and skips work when inputs have not changed. Closures break that model. Stable ViewModel references fix it.