inessential by Brent Simmons

Why NetNewsWire Is Fast

NetNewsWire is fast because performance is one of our core values. Being fast is part of the very definition of the app.

I suspect that it’s hard to do this any other way. If you take a month or two to speed things up, from time to time, your app will always be — at best — just kind of heading toward satisfactory, but never to arrive.

The best general advice I can give is just this: make sure performance is part of the foundation of your app. Make sure it‘s part of every decision every day.

Make sure, in other words, that performance isn’t just a topping — it’s the pizza.

Below are some of the specific reasons NetNewsWire is fast. Because NetNewsWire is — like many apps these days — basically a fancy database browser where data comes from the web, some of these will apply to other apps.

The below items are in no particular order.

Fast RSS and Atom Parsing

The most painful way to parse XML is with a SAX parser — but it’s also how you’ll get the best performance and use the least memory. So we use SAX in our RSParser framework.

On my 2012 iMac, parsing a local copy of some past instance of the Daring Fireball Atom feed — relatively large at 112K in size — happens in 0.009 seconds.

That’s fast, but we do another thing as well: run the parser in the background on a serial queue. Since parsing is a self-contained operation — we input some data and get back objects — there are no threading issues.

Conditional GET and Content Hashes

The parsers are fast — but we also do our best to skip parsing entirely when we can. There are two ways we do that.

We use conditional GET, which gives the server the chance to respond with a 304 Not Modified, and no content, when a feed hasn’t changed since the last time we asked for it. We skip parsing in this case, obviously.

We also create a hash of the raw feed content whenever we download a feed. If the hash matches the hash from the last time, then we know the content hasn’t been modified, and we skip parsing.

Serial Queues

The parser isn’t the only code we run on a serial queue. When an operation can be made self-contained — when it can just do a thing and then call back to the main thread, without threading issues — we use a serial queue if there’s any chance it could noticeably block the main thread.

The key is, of course, making sure your operations are in fact self-contained. They shouldn’t trigger KVO or other kinds of notifications as they do their work.

(A simple example of a background thing, besides feed parsing, is creating thumbnails of feed icons.)

We Avoid the Single-Change-Plus-Notifications Trap

Here’s an example of a trap that’s easy to fall into. Say a user is marking an article as read. Calling article.read = true triggers, via KVO or notifications or something, things like database updates, user interface updates, unread count updating, undo stack maintenance, etc.

Now say you’re marking all articles in the current timeline as read. You could call article.read = true for each article — and, for each article, trigger a whole bunch of work. This can be very, very slow.

We have specific APIs for actions like this, and those APIs expect a collection of objects. The same API that marks a single article as read is used to mark 10,000 articles as read. This way the database is updated once, the unread counts are updated once, and we push just one action on the undo stack.

Coalescing

We also try to coalesce other kinds of work. For instance, during a refresh, the app could recalculate the unread count on every single change — but this could mean a ton of work.

So, instead, we coalesce these — we make it so that recalculating unread counts happens not more often than once every 0.25 seconds (for instance). This can make a huge difference.

Custom Database

For an app that is, again, just a fancy database browser, this is where the whole thing can be won or lost.

While Core Data is great, we use SQLite more directly, via FMDB, because this gives us the ability to treat our database as a database. We can optimize our schema, indexes, and queries in ways that are outside the scope of Core Data. (Remember that Core Data manages a graph of objects: it’s not a database.)

We use various tools — such as EXPLAIN QUERY PLAN — to make sure we’ve made fetching, counting, and updating fast and efficient.

We do our own caching. We run the database on a serial queue so we don’t block the main thread. We use structs instead of classes, as much as possible, for model objects. (Not sure that matters to performance: we just happen to like structs.)

To make searching fast, we use SQLite’s Full Text Search extension.

I could, and probably should, write more articles going into details here. The database work, more than anything else, is why NetNewsWire is fast.

Sets and Dictionaries

We often need to look up things — a feed, given its feedID, for instance — and so we use dictionaries frequently. This is quite common in Mac and iOS programming.

What I suspect is less common is use of sets. The set is our default collection type — we never want to check to see if an array contains something, and we never want to deal with duplicate objects. These can be performance-killers.

We use arrays when some API requires an array or when we need an ordered collection (usually for the UI).

Profiler

Instead of guessing at what’s slow, we use the profiler in Instruments to find out exactly what’s slow.

The profiler is often surprising! Here’s one thing we found that we didn’t expect: hashing some of our objects was, at one point, pretty slow.

Because we use sets quite a lot, there’s a whole lot of hashing going on. We were using synthesized equality and hashability on some objects with lots of string properties — and, it turns out, hashing strings is pretty darn slow.

So, instead, we wrote our own hash function for these objects. In many cases we could hash just one string property — an article ID, for instance — instead of five or ten or more.

No Stack Views

My experience with stack views tells me that they’re excruciatingly slow. They’re just not allowed.

No Auto Layout in Table Cell Views

When people praise a timeline-based app like NetNewsWire, they often say something like “It scrolls like butter!” (I imagine butter as not actually scrolling well at all, but, yes, I get that butter is smooth.)

While we use Auto Layout plenty — it’s cool, and we like it — we don’t allow it inside table cell views. Instead, we write our own layout code.

This is not actually difficult. Maybe a little tedious, but laying out a table cell view is pretty easy, really.

I figure that optimized manual layout code is always going to be faster than a constraint solver, and that gives us an edge in smooth scrolling — and this is one of the places where an otherwise good app can fall on its face.

And: because that layout code doesn’t need a view (just an article object and a width), we can run it at any time. We use that same code to determine the height of rows without having to run an Auto Layout pass.

Caching String Sizes

Text measurement is slow — slow enough to make even manual layout too slow. In NetNewsWire we do some smart things with caching text measurement.

For example: if we know that a given string is 20pts tall when the available width is 100 and when the available width is 200, we can tell, without measuring, that it will be 20pts tall when the available width is 150.

Summary

There’s no silver bullet. Making an app fast means doing a bunch of different things — and it means paying attention to performance continuously. 🍕

Focusing

Tomorrow’s the first day at my new job. Exciting!

Starting a new job has led me to look at my entire list of responsibilities — which is too long — and figure out what I need to drop so that I can pay enough attention to the projects that need it most.

My most important projects (outside of my job) are NetNewsWire and this blog. This blog, because, well, blogging is part of how I breathe. And NetNewsWire because I love the app — and it’s a real thing in the world now, with users, a team of developers, and great features coming up.

I wanted to do another half-dozen or so apps alongside NetNewsWire, starting with Rainier, but I’m dropping development on those so I can concentrate entirely on NetNewsWire. This is personally disappointing, but it’s honest: I just don’t have time for Rainier and these other apps. Work on these would take away from NetNewsWire, and that would be wrong.

Another move I’m making: Manton Reece has agreed to take over the repo and website for JSON Feed. I’ve been the bottleneck here with a 1.1 version, and I shouldn’t be. Manton will take care of this way better than I’ve been able to. (I hope to get everything transferred over to Manton in the next few weeks.)

My New Job

As of this morning the ink is all dry, and I can happily report that my new job is at Audible. I’ll be an architect on the mobile team.

I’m very excited for this job! It’s perfect for me in so many ways — not least that it’s about books.

My plan is for this to be my last job — I plan to work at Audible until I retire. I start Monday. 🐣🐥🕶

NetNewsWire 5.0.1 for iOS Released

While I’ve been job-hunting, the mighty NetNewsWire team has kept rolling — and today we published the first update to the iOS app.

This update fixes bugs, makes the app faster, and adds polish. Read the (rather lengthy) change notes for the full scoop.

We did add one new feature: on the settings screen you can choose which color palette to use: go with the current system setting or specify light or dark.

If you’re already running NetNewsWire, it should update in the normal way. If you haven’t tried it yet, go get it — for free — on the App Store.

My Mac App Store Debate

The question of publishing NetNewsWire on the Mac App Store won’t be decided until the minute that it’s actually published there.

If it ever is, that is. I go back and forth on it.

Here’s the thing to remember: our goal is to get as many people using RSS readers as possible. Period. Keep this goal in mind.

Seems Obvious

Publishing on the Mac App Store would mean that some people would see the app who might never have seen it otherwise.

There are also people who, due to personal or workplace policy, download apps only from the Mac App Store.

Publishing on the Mac App Store seems like a no-brainer, then. We’d get more people using RSS readers — we’d further our goal.

But it’s not so simple.

Trade-offs

As with everything else, there are trade-offs. There are costs and benefits.

The benefit is reaching more people. There are several costs.

Some are right up front: we’d have to sandbox the app and test it. We’d have to do a set of screenshots for the Mac App Store; we’d have to write the description text for the page.

But I don’t mind one-time costs that much when there’s a solid benefit.

There are ongoing costs, though: we’d have two configurations of the Mac app, one for the Mac App Store and one for direct download, and we’d have continue to maintain and test both. This is kind of a pain, but not terrible.

The Real Cost

There’s a cost that’s worse than the technical and testing costs: I would have to deal personally with the stress and uncertainty of a second App Store. The NetNewsWire team is amazing and does a ton of great work — but the team can’t do this part. It’s on me.

The issue with the default feeds reminds me that, at any time, even for a small bug-fix update, App Store review may decide that an app can’t be published as-is for some reason.

You‘d be right to think that, with an issue like this, it would come up the same on both App Stores — solve it in one place and you’ve solved it in both. It’s not like I’d have double the issues.

But sometimes the issue actually is platform-specific. For example: NetNewsWire Lite 4.0 for Mac was held up by Mac App Store review for three weeks due to a bug in WebKit. (Yes, this was nine years ago.)

This is supposed to be fun. It’s work that I love doing for a great cause. And I just keep thinking that dealing with the iOS App Store is enough to ask of me, and there’s no requirement that I go through this with the Mac App Store too. The personal cost is just too high.

Other Ways to Achieve Our Goal

We can achieve our goal in other ways: ship Feedly syncing on the Mac, ship iCloud syncing on both apps, continue making the app more appealing to more people. Do more marketing.

In other words, publishing on the Mac App Store is not the only lever we have, and I’m leaning toward just not doing it. At least not this year.

We’ve got other, better things to do — and I’ll enjoy those things a hell of a lot more, and I think you will too.

🐣🎸

On Talking with the Duet Folks

One of the teams I talked to during my job hunt — and that didn’t put me through a scary tech interview :) — was the folks at Duet.

They make an app where you can use an iPad as a second screen for your Mac. They also support Android and Windows. (You should check out their apps.)

I thoroughly enjoyed talking with the team, and I believe I would have been very happy working there.

They’re looking for a Mac developer. Maybe you? Get in touch via their contact page.

More on the Default Feeds Issue

Here’s the latest on the story from yesterday.

NetNewsWire 5.0.1 for iOS was approved for the App Store this morning, and I assumed that was the end of it. I figured this whole thing was just an error.

But later today I heard from Apple that, while this latest version has been approved, the app is now under further review for this issue.

This isn’t quite over yet — but at least we could ship 5.0.1, so that’s cool, and I’m glad.

More Details

The issue really is about the default feeds. They’re added by default on the first run of the app.

Apple suggested some options — things I could do if, after further review, they decide that I need to bring the app into legal compliance:

  • Provide documentation (to Apple) expressing permission to use those feeds as defaults
  • Have the user, on first run, pick from a list of these feeds
  • For these feeds, show only a title, and then link out to Safari

For now I’m not doing any of those things, since Apple’s review is ongoing. I’ll wait for the review to complete.

If the review completes and I do need to do something, I’ll take the first option: I’ll get the necessary documentation.

(Yes, I could change the UX instead. But I don’t want to — the app works the way I think is best. You could debate whether I’m right or wrong on that point, but there’s no debating that this is the UX I want.)

I’m Not Actually Against Getting Permission

As I wrote on the NetNewsWire FAQ about the default feeds:

We change the feeds from time to time. We don’t have any arrangements with the feed owners, though we usually ask permission — unless it’s something like Daring Fireball or Six Colors where it would obviously be no problem.

The authors of Daring Fireball, Six Colors, and a few other sites are friends, and I don’t need to bug them to ask permission. There are other default feeds where I know the people less well (or not at all), and I have asked permission from people — not because I was worried legally but because it seemed like basic courtesy. I don’t think anyone’s ever said no, but I did want to give them the chance.

But can I find all these conversations, and can I turn those conversations over to Apple without asking the other parties?

I don’t think so. So this would mean going through and getting explicit permission from a dozen-ish different people and turning copies over to Apple.

Which is fine. I can do that if Apple decides they need that documentation. It’s not onerous.

What Still Rubs Me the Wrong Way

I’m trying to figure out what bothers me. I think there are two things.

One is just that the App Store has always seemed rather arbitrary. The guidelines don’t even have to change for unseen policies to change, and it’s impossible to know in advance if a thing you’re doing will be okay and stay okay. (Recall that NetNewsWire has been doing the same thing with default feeds for 18 years.)

This gets really tiring, because every time we submit an app — even just a bug-fix release, like 5.0.1 is — I have to deal with the anxiety as I wonder what’s going to happen this time.

The other issue is a little harder to explain, but it goes like this:

If a site provides a public feed, it’s reasonable to assume that RSS readers might include that feed in some kind of discovery mechanism — they might even include it as a default. This is the public, open web, after all.

Now, if NetNewsWire were presenting itself as the official app version of Daring Fireball, for instance, then that would be dishonest. But it’s not, and that’s quite clear.

To nevertheless require documentation here is for Apple to use overly-fussy legal concerns in order to infantilize an app developer who can, and does, and rather would, take care of these things himself.

In other words: lay off, I want to say. I’m an adult with good judgment and I’ve already dealt with this issue, and it’s mine to deal with.

Archive