Skip to content
Development

Fixing History's load path, then catching myself over-commenting the fix

By Victor Da Luz
iosswiftperformancedev-logdeep-cut-atlas

This app was later renamed Deep Cut Atlas. It’s called “Discoverer” throughout below, because that’s what it was called on the day this happened.

Third perf pass this week on Discoverer, this time on the History tab. The same review that caught the Discover tab’s issues flagged three problems here too, all in the code that loads and paginates your recently-played tracks.

The first one was familiar by now: three fetches that should have been independent were running one after another. Playlist contents, then a full scan of library album keys, then the first page of recently-played tracks. All sequential, all blocking the screen from showing anything. The twist this time was that one of those three fetches, the library key scan, wasn’t even needed to render the list. It only mattered later, for gating a button in a detail sheet that opens when you tap a row. It was loading up front purely out of habit, not because the screen needed it.

The fix had two parts. First, kick off the playlist fetch and the first page fetch at the same time using a plain unstructured task, the same workaround from a few days ago since Swift’s cleaner async let syntax doesn’t work against this kind of main-actor service. Second, move the library key scan to run lazily, once per session, after the screen is already showing something. There was already a working example of this exact pattern elsewhere in the app, on the Playlist tab, so I copied its shape instead of inventing a new one.

The other two problems were smaller but showed the same shape of mistake: doing repeated work that could have been done once. One spot recomputed a filtered list every time a row scrolled into view, instead of once per screen draw. Another recomputed a running count from scratch on every page of results instead of just keeping a running tally. Neither is dramatic on a small list. Both turn into real, felt lag as a list grows, because the cost scales with the square of the list size instead of linearly.

The part I didn’t expect to be writing about

Partway through, my one-line fixes had grown a small essay of “why” comments attached to each one, explaining the reasoning behind every change. It felt responsible at the time. Then the file tripped a line-count linter rule and I had to go back and trim nearly everything I’d just written, function by function, just to get the file back under the limit. That’s when it landed: I wasn’t writing comments because the code needed them. I was writing them because I’d just finished reasoning through something tricky, and typing it out felt like proof of the work. The code didn’t get harder to read without them. If anything, some of the trimmed versions read cleaner, because a comment that just restates “we do X because Y” often means X and Y should have been a better name in the first place.

I ended up writing myself a stricter rule about it before moving on: before adding any comment, check whether removing it actually loses information a reader can’t get any other way, whether it’s covering a genuine exception rather than routine logic, and whether a better name would have made it unnecessary. If it doesn’t clear all three, don’t write it. Small thing, but it’s already changing how the next diff looks.

Related reading