inessential by Brent Simmons

March 2021

NetNewsWire 6.0 for Mac

NetNewsWire 6.0 for Mac has shipped! See the blog post for a download link and list of changes.

The team of volunteers has done wonderful work — during multiple crises, including a pandemic — and produced one of the great NetNewsWire releases. Their reward is hearing that you love the app. 🐣🐥🎸

Next up for the team: we’re working on NetNewsWire 6 for iOS. The good news is that most of the code is shared between Mac and iOS — for instance, the iCloud syncing code is the same, and we’ve got this ship-worthy. But we still need to test and fix bugs. We’ll make an announcement on the NetNewsWire blog once we’re doing TestFlight builds.

Benefits of NetNewsWire’s Threading Model

In my previous post I describe how NetNewsWire handles threading, and I touch on some of the benefits — but I want to be more explicit about them.

It Reduces Bugs and Makes the App Stable

I would not be surprised, were it possible to know, that the largest trigger for bugs and crashing bugs (among iOS and Mac apps) is threading issues of various kinds.

Our threading model eliminates this category.

We have zero known threading bugs or crashes in production, and they’re extremely rare in development. When they appear in development they’re due to a mistake in following the model. (Something like forgetting to dispatch to the main queue when calling a callback, for instance.) They’re quickly and easily fixed.

It Makes the App Faster and More Efficient

The app does not suffer from thread explosion, which is bad for performance. Ever stop your app in the debugger and notice that it has dozens of threads? Or maybe notice that in a crash log? Not an issue for NetNewsWire.

But, more importantly, we don’t hide performance and efficiency issues by moving work to a background queue. Instead, we use the profiler in Instruments to figure out what’s slow, and then we fix it. Only after doing that would we consider moving work to a background queue.

It Saves Developer Time

Things NetNewsWire developers don’t have to do:

  • Diagnose and fix threading bugs and crashes — which are often excruciating-to-impossible to reproduce and difficult to fix
  • Think about how to do background work (there’s a simple model for how to do it)
  • Figure out how to protect shared mutable state

This gives us more time — which is super-valuable in an open-source, all-volunteer project — to do work that improves the lives of the people who use the app.

It also improves the experience of our developers, who can concentrate on the feature they’re working on instead of on how the feature can live safely in a multithreaded universe.

Best of all: nobody is spending time tracking down a maddening threading bug that never happens on their machine, and then implementing a speculative fix — only to find later that it’s not the fix but now, actually, there’s a new crashing bug, which might have been triggered by that “fix”… and so on, forever.

Developer morale is important!

How NetNewsWire Handles Threading

NetNewsWire is mostly not multi-threaded. Here’s what we do:

Run Most Code on the Main Thread

The default place for all of our code — UI code and otherwise — is the main thread. Synchronous code running on the main thread is the easiest code to reason about and is the least likely to trigger weird, intermittent bugs.

In other words: we try, as much as possible, to not use queues or threading at all. I can’t emphasize this enough: the best way to handle concurrency is just to not do it.

Communicate Between Components on the Main Thread

Every notification and every callback happens on the main thread.

Though a given object (or small system) may use a serial queue internally, it never, ever lets that fact leak out beyond its own boundaries.

Use Serial Queues for Pure Functions That Take Time

Parsing an RSS feed is a pure function. Since it does take some time (though less than you think), it can be done on a serial queue, in the background. The result is returned via callback on the main thread (see above).

These kinds of things are great for background processing because they’re perfectly isolatable — parsing a feed affects no state. It doesn’t do any notifications. All it does is callback, once done, with an object.

There are other examples (decoding JSON or image data) that are great for this, and which we handle the same way.

Use Serial Queues for Database Access

Our model objects are plain ol’ classes and structs. The APIs for reading and writing are main-thread-only, and they call back asynchronously but always on the main thread with a result.

Inside the database code, though, is a serial queue. It’s completely internal to that code, though, and utterly invisible to the rest of the app.

When the database code sends any notifications, it does so on the main thread. It doesn’t otherwise affect any app state.

Use Asserts and Preconditions

If you do a search on Thread.isMainThread, you’ll find a bunch of these…

precondition(Thread.isMainThread)

…and…

assert(Thread.isMainThread)

…and one example of XCTAssertTrue(Thread.isMainThread). We don’t otherwise test if we’re in the main thread.

(I would not mind having a whole lot more of precondition(Thread.isMainThread) than we do now.)

The Result

You may be skeptical about this model, but I’ll remind you that NetNewsWire is responsive and freakishly fast. It’s also — by far — the most stable and bug-free app I’ve ever worked on in my long career. And a big part of that is our threading model.

One of the nice things about this model is that a developer knows, just by looking at where they are, what thread the code is running on. It’s almost always main. But if you’re working on the RSS parser or similar, you know that it literally doesn’t matter, and if you’re working on the database or similar, it’s obvious that you’re using a serial queue.

As we adopt Combine, SwiftUI, and future Swift language changes to support concurrency, we will continue to use this model. The details of our implementation may change, but the model will remain the same: use the main thread everywhere except in a few cases, and make sure that those cases cannot leak knowledge or behavior of their queues outside themselves.

Advice

Some developers I’ve known seem to think that being good at concurrency makes them badass. Others seem to think that senior developers must be great at concurrency, and so they should be too.

But what senior developers are good at is eliminating concurrency as much as possible by developing a simple, easy, consistent model to follow for the app and its components.

And this is because concurrency is too difficult for humans to understand and maintain. Maybe you can create a system that makes extensive use of it, and have it be correct for one day. But think of your team! Even if you’re a solo developer, you and you-plus-six-months makes you a team.

I know you’re worried about blocking the main thread. But consider this: it’s way easier to fix a main-thread-blocker than it is to fix a weird, intermittent bug or crash due to threading.

With a main-thread-blocker, use the Time Profiler in Instruments and figure out what’s going on. It may be that something does need to move to a background serial queue — or, more likely, there’s a data structure or algorithm that could be improved, or you find your app is doing unnecessary work (or both).

How To Get There From Where You Are

I suspect most apps don’t have a clear model for concurrency. If yours does, then great! Skip the rest of this post. :)

If you want to move to the model I’ve described above, I’d start with a few things.

  • Identify code that should be main-thread-only and use assert(Thread.isMainThread) in that code
  • Turn on Xcode’s Main Thread Checker
  • Make sure that every Notification that your app posts is posted on the main thread. In notification handlers, add an assert(Thread.isMainThread)

If you don’t use Notifications, apply that advice to KVO or whatever else you might be using.

The reason I pick Notifications and KVO is because they’re a kind of spooky action at a distance — when you’re working on the Notification handling side, you don’t know what thread the Notification was posted on, and developers tend to assume it’s the main thread. This is a common place for threadedness to leak, which causes bugs and crashes. (Accidental multithreading is the scourge of our platform.)

The next step is probably to look at the places where you’re using queues and find out which of those could be moved to the main thread. And, when you can’t move to main, build better walls: make APIs that call back on the main thread, and don’t let the fact of those queues leak out from behind the API. Don’t let threadedness leak.

You can get there incrementally.

In the meantime you may find that you’re having to dispatch to the main queue rather often as a defensive measure.

Ideally, you’re only dispatching to the main queue when it’s part of this threading model (when posting a notification from a background queue or calling a callback) and never as a defensive measure.

However, it may be a while before your app gets there, and that’s okay. (Tip: do consider the case where sometimes that call to DispatchQueue.main.async is happening in code already running on the main thread. The async part is real, and you may not always want that: sometimes you may want code to run immediately when already on the main thread. And sometimes not.)

In the end: change all your assert(Thread.isMainThread) to precondition(Thread.isMainThread) — make your app crash in production when the threading model is violated. Being able to do that — knowing that it won’t ever trip — is a great place to be. 🐣🐥🎉

New in NetNewsWire 6: iCloud Syncing

We’ve added — in addition to support for a bunch of online RSS systems — iCloud syncing in NetNewsWire 6. (Latest beta 6.0b3 is recommended at this writing.)

This is great for people who use only NetNewsWire for reading feeds — it means you don’t need an additional service or login aside from iCloud itself, which you’re almost certainly already using.

Here are some things to know…

It Reads Feeds Directly

An iCloud sync account is like an On My Mac account in that it reads feeds directly from the sources instead of going through a separate RSS system such as Feedbin or Feedly.

Many people prefer this, for privacy reasons — it means their feeds list isn’t stored on some RSS syncing system.

But some people prefer — also for privacy reasons — not to read feeds directly: they like having a system that goes directly to the sources. This way the sources don’t see their requests.

Consider your own preference when choosing to use iCloud sync or not.

Another issue with this model: you could miss articles in fast-moving feeds. (This is true for both iCloud and On My Mac accounts.)

Imagine a feed that updates a hundred times a day. Say you take a Monday off and don’t launch NetNewsWire on any device. By Tuesday, when you launch NetNewsWire, some of the articles from Monday have already fallen off the feed. You’ll never see those articles.

If you used an online system you would not miss those, because online systems never take a day off.

It Works with NetNewsWire Only

Other RSS readers include iCloud syncing. But you can’t sync those apps with NetNewsWire via iCloud — Apple’s CloudKit doesn’t allow for that. Each app gets its own storage, and other apps can’t see that storage. (Which makes sense.)

If you want to use NetNewsWire on one machine and another app on another, you’ll need to choose an RSS syncing system that both apps support. (Which shouldn’t be difficult.)

NetNewsWire Supports Multiple Accounts

This is not new in NetNewsWire 6, but it’s worth pointing out: you can have an iCloud sync account and (for instance) a Feedbin account. NetNewsWire is designed for this, just as Mail is designed for multiple email accounts.

Being able to choose which feeds are synced where is powerful.

But do note that you can have only one iCloud account in NetNewsWire. (Because of how iCloud works.)

It May Be Slow at First

NetNewsWire itself is very fast. But syncing happens at the speed of iCloud — and iCloud sometimes throttles the app: it tells NetNewsWire to back off and try again later.

This can be especially noticeable when you’re just starting off with iCloud in NetNewsWire, or whenever you add a big number of feeds, because there will be a ton of data to upload.

So: be patient right at first and whenever you’re adding a bunch of feeds to your iCloud account.

We’re Shipping the Mac App Before the iOS App

This feature will come to NetNewsWire for iOS too, of course. Our plan is to ship NetNewsWire 6 for Mac first, and then start TestFlight builds for the iOS app, and then ship NetNewsWire 6 for iOS.

The good news: the iCloud sync code is shared between the two apps, which means it’s already getting a thorough test.

You might ask why we’re not shipping Mac and iOS at the same time. Our wonderful, remarkable team of volunteers — working during their spare time during a pandemic and multiple other crises — could have handled shipping both at the same time. But splitting it up this way is my call — it’s easier for me personally to manage, and I ask for your understanding. Thanks!

The Hottest of All Xcode Tips

It’s come to my attention that not everyone knows this. But everyone needs to know this.

In Xcode’s Organizer, in the Crashes section, you can right-click or ctrl-click on any row and choose Show in Finder. This will reveal a .crashpoint file — do a Show Package Contents and then dig in further. You will find .crash files with the full crash logs, which provide a lot more info than what you see in Organizer.

(Bonus tip: I just drag the .crashpoint package onto BBEdit, which shows the contents in its sidebar, where I can click on different .crash files. I assume other text editors have a similar feature.)

New in NetNewsWire 6: Syncing Via BazQux, Inoreader, NewsBlur, The Old Reader, and FreshRSS

NetNewsWire 6 — currently in beta (Mac for now) — adds support for a bunch of RSS sync systems: BazQux, Inoreader, NewsBlur, The Old Reader, and FreshRSS. (NetNewsWire already supported Feedly and Feedbin: this makes the list a lot longer.)

If you’ve held off on checking out NetNewsWire because you use one of the above, well, you don’t have to wait any more. Unless you want to wait for it to get out of beta, which is completely sensible. 🐈

Not new in NetNewsWire 6, but worth remembering: NetNewsWire supports multiple accounts. You can have Feedly and Inoreader and FreshRSS, for instance. You can even have multiple of each. (You’d need multiple logins, of course.)

(We’re getting closer to our goal of supporting as many as possible of the existing systems. While I can’t guarantee we’ll get to every single one, we’re certainly trying.)

How to add an account

Open Preferences. Click the Accounts icon. Click the + button near the bottom left of the window. You’ll see a list of accounts as in this screenshot. Pick the one you want to add, and then follow the prompts for authentication and setup.

Bonus Tip

If you find an OPML file somewhere and just want to check it out without adding it to your current feeds, you can use NetNewsWire’s support for multiple accounts. (This is not new in NetNewsWire 6: this works in NetNewsWire 5 too.)

Create a new On My Mac account (you can have more than one) just for this OPML file, and then choose that account when importing the OPML file. That way you can see what’s in it, and you can copy any feeds you like into another account. When you’re done, you can just delete the temporary account (or make it inactive).

Example: if you’re an iOS developer, you might want to import some of the OPML files at Dave Verwer’s iOS Dev Directory.

New in NetNewsWire 6: Twitter Search Feeds

New in NetNewsWire 6 — currently in beta (Mac version, for now) — is the ability to add Twitter searches and read them as if they were any other kind of feed.

Here’s how:

Open Preferences > Extensions and add your Twitter account. Close the Preferences window.

Then, from the menubar, choose File > New Twitter Feed. In the window that appears, make the type Search, provide a search string, choose an account and folder, then click Add.

(Note: these work with iCloud and On My Mac accounts only. Some of the other syncing services support special Twitter feeds too, but you have to set them up via their website.)

What’s cool about this: you can watch for mentions of whatever you want, and those come to you in the same app where your other feeds live.

I have a search feed set up for mentions of NetNewsWire, as you’d imagine. Here’s a screenshot of what it looks like in my copy of NetNewsWire. 🐣

NetNewsWire 6.0a6 for Mac

We’ve been doing a bunch of work on the crash log catcher — we even set up a Linode instance for just this one thing. (It’s the first server I’ve set up in 20 years!)

The thing is, we’re pretty much not getting crash logs for 6.0 test builds. (Less than half-a-dozen for 6.0a4, none at all for 6.0a5.) Which makes me suspicious of our system for catching and reporting crashes.

But every test I can come up with shows that the system is working just fine. So either nobody’s running these, or the app is just that stable.

Anyway — here’s the latest.

Aunt Jen’s and Uncle Pete’s Live Online Show

My aunt Jen and uncle Pete have adapted their show “Live From New York! He’s A Prom Date!” for live-streaming (yes, a live performance over the web). Tickets are just $10 or $15 for a couple.

Sheila and I saw a rehearsal and loved it.

You can get tickets here and you can read more about it in the Pioneer Press.

The show is a true story: 16-year-old Jen didn’t have a prom date, and her mom got her on an episode of the Sally Jessy Raphael show. And… well, you should see it. You’ll laugh. You might cry a little. And you just might love it as much as we did. 🎉