inessential by Brent Simmons

January 2020

More Bug Math

When an app is under 100 bugs, you can start getting an idea for when it will be done.

The idea will be wrong, of course, but it’s still an idea.

Numbers

I started keep tracking of the number of open bugs on Jan. 9, when we had 41 open bugs.

Now, 19 days later, we’re down to 12 open bugs.

That does not mean we’ve fixed just 29 bugs: we’ve probably fixed three times that many. And that’s because, this late in the game, we have a lot of testers and bug reports — and also because any bug fix could result in another bug. (Or two. Or three.)

However: the math says we’re netting 1.5 bugs fixed per day. In other words — despite the new bug reports — on average, every day we’re 1.5 bugs closer to shipping. That’s a pretty good rate of progress.

This tells me that we’ll be down to zero bugs in eight days. (We’ll fix a lot more than 12 bugs in those eight days, but in the end we’ll have no bugs left.)

Of course that’s not really true. This is app development, after all — a process so uncertain that it makes Chaos blush.

But still, it gives us an idea of when we might be done. Pretty confident it will be in 2020.

Bug Math

The NetNewsWire team continues its amazing run — we just released build 31.

When we released build 30 two days ago, we were down to seven bugs.

In the last two days, the team fixed nine bugs.

How many bugs are left? Could it be -2? Nope: it’s 12.

7 - 9 = 12. That’s app-writin’ for ya.

Crash Math

We’d like to ship NetNewsWire 5.0 for iOS in the first quarter of this year. The app is really close, but there are a few bugs to fix, including some crashes.

We’re sticklers about crashes: while there’s no way to guarantee the app will never crash — because there are bugs in other parts of the system that we can’t control — we want to get the number of crash reports as close to zero as we can. Ideally we’d go days or weeks between seeing a crash report.

This is about the craft of app-making. It’s about being responsible.

But it’s also about math. Consider this:

NetNewsWire for iOS could have 100,000 users. It’s relatively high-profile, in a popular category, and free.

So if we get it down to, say, a 1% chance that a given user will hit a crash on any given day, that sounds pretty good, right?

But that 1% chance means we’d get 1,000 crash reports per day. In other words, a 1% chance is very, very bad.

If we get it down to a 0.1% chance, we’d still get 100 crash reports per day. At that level, a given user could go, on average, 500 days between crashes. Which sounds great! Sounds like a super-stable app!

But that would mean 100 crash reports a day, which is still a massive number of crashes.

We need to do way better than that. (We will.)

Fair Isle Brewery Opens Today

The Fair Isle Brewing company’s tasting room opens today! (One of the partners is a friend: the one in the photo with the big gray beard.)

I had a preview a couple weeks ago. The beers are really, really good. 🎩🐥

The beers were…

…created with ingredients farmed and foraged in the Pacific Northwest and fermented with our house blend of wild and feral yeasts and bacterias.

The name “Fair Isle” sounds like “feral.”

PS Here’s their website.

Jeff McLeman

It is my sad duty to report that Jeff McLeman — whose work you’ve used, even if you don’t know it — suffered injuries from a very bad fall, and soon after passed away as a result of those injuries.

Jeff was a long-time Seattle Xcoder, before recently moving away, and he was a beloved friend to me and many people.

He was also a walking computer history museum — he was a little older than me, and he’d worked on a number of projects during that heroic age when people wrote new operating systems because they were needed. I learned from every single conversation with him.

There’s a NetNewsWire connection, too. He worked at Black Pixel for a while, and — among many other things — he played a significant role in their shipping NetNewsWire. And when it came back to me, he was super-happy for me, and he encouraged me frequently and cheered on our work.

He was exactly the kind of person you wanted in your corner — and it seemed like he was in everybody’s corner.

We’re all shocked and saddened, and we miss him. I miss him. Our thoughts go to his wife and family.

You’ve probably heard his voice before: he was on Debug #35: Jeff McLeman on porting kernels. I’m re-listening to it right now.

Uniform Type Identifier Bug on iOS — and How It Affects NetNewsWire

In NetNewsWire you can import your subscriptions from an OPML file. This is the standard way RSS readers work: you can export your subscriptions as an OPML file, and then import them into a different RSS reader.

This way you can move from reader to reader without having to redo your subscriptions list. It’s a good thing.

But in NetNewsWire for iOS (currently in beta) this sometimes doesn’t work because, for some people, the system won’t let you select an OPML file to import. (This does not happen on Macs.)

Here’s What Happens

In our app, we declare an OPML type: org.opml.opml with a file suffix .opml.

The org.opml.opml part is a Uniform Type Identifier (UTI), which is Apple’s way of defining document types.

When the user wants to import subscriptions from an OPML file, the app presents a document picker, and the app tells the picker that the user can select files of type `org.opml.opml.

We don’t want the user to select, for instance, a plain text file, or a Pages document, or a GIF, or whatever. We just want to make OPML files selectable.

This works great — except when, for some people, it never works: it doesn’t allow them to select OPML files, and so they can’t import their subscriptions.

It took a while to figure out the problem, but we did.

Here’s the Problem

We found that if a user has another app that declares an OPML UTI — and that UTI doesn’t match ours — then the document picker will not see those OPML files as the type we accept (org.opml.opml) but as something else.

So far we’ve found two other definitions for OPML: com.reederapp.opml and unofficial.opml (from Overcast). (To be clear: Reeder and Overcast are tremendous apps, and this situation is not at all their fault.)

For someone with one of those apps installed, the system sees a .opml file suffix and decides the file is of type com.reederapp.opml or unofficial.opml. Which doesn’t match our type — org.opml.opml — which means the user can’t select the file. Even though the file suffix is .opml.

One Possible Solution: Declare All the Types

When presenting the document picker, we could declare that we allow com.reederapp.opml and unofficial.opml.

But here’s the rub: those are just the two we know about. We would need to do research on all the RSS readers, podcast players, and outliners to find all the various declarations of the OPML type.

And, furthermore, every time a new declaration appears in a new app we’d have to add support for that one too. (In the meantime, it would break subscriptions importing, for people with that app, until we do add support.)

Another Possible Solution: Tell the System that All Text Files Are Selectable

We could just let the user select any file that’s a text file. OPML files are text files, after all.

But then, so are HTML pages. And Markdown files. And any plain-text documents you may have.

This breaks the principle that you let users select only the kind of file that’s appropriate.

But that’s the solution we’re going with. I don’t like it. I’m not even completely sure it will work in all cases, but I am sure that it makes for a sub-par UI.

I don’t see that we have a choice. Please tell me if I’m wrong!

PS We found a related bug report for this from 2012: Apps can declare bad UTIs that redefine what a file extension conforms to.

On Replacing OperationQueue

We fixed our mystery KVO crash by writing a replacement for OperationQueue.

Well, sort of a replacement. It’s not a drop-in replacement, because what we really wanted was slightly different from OperationQueue.

OperationQueue does a few things:

  • Manages a queue of operations
  • Runs operations in multiple threads
  • Supports dependencies — operations can be dependent on other operations
  • Supports canceling operations

But that’s not exactly what we want. We want something that:

  • Manages a queue of operations
  • Runs operations on the main thread
  • Supports dependencies — operations can be dependent on other operations
  • Supports canceling operations

Note the difference: we want to run operations on the main thread. That may sound crazy to you, but I’ll explain why shortly.

Note also some characteristics of OperationQueue:

  • Uses KVO (is finicky and crashy)
  • Subclassing is allowed and expected (of Operation/NSOperation)

But that’s not what we want either. Instead, we want:

  • Low-tech, non-crashy notifications
  • No subclassing

Canceling Is the Most Important OperationQueue Feature

Recall that OperationQueue — at the time just NSOperationQueue — came out before Grand Central Dispatch (GCD), and it was the best way to get code off the main thread.

But these days we do have GCD, and when we want to run code on a background thread, we use GCD. (And, often, a serial queue.) We don’t need OperationQueue just to be able run code on multiple threads.

Instead, the important thing about OperationQueue, the reason to use it instead of just using GCD, is the ability to cancel operations. This is critical in an app where conditions change — the network might fail, the app might move to the background, databases may get closed. Canceling is critical.

This is why we didn’t just stop using OperationQueue in favor of GCD. This is why we needed to write our own thing that supports canceling.

Why Run Code on the Main Thread

I’ve written before about writing main-thread-only code — it’s a paradise when you don’t have to think about concurrency.

You may think you’re good at writing thread-safe code, but are you 100% good? Is everyone on your team as perfect as you are? And, when multithreading bugs inevitably do happen, how quickly can you reproduce them and fix them?

Our default position is to write code that is not thread-safe — and not even safe to run on any thread besides the main thread.

That said, we do have a number of APIs that do async things and then call back to the main thread. Networking, for instance. Updating or querying a database. Parsing feeds.

Even though our operations run only on the main thread, they can — like any other code — call an API that does something on a background thread and then calls back to the main thread.

An operation’s code might look conceptually like this:

func run() {
	// On the main thread right here.
	// doSomeAsyncThing uses GCD
	doSomeAsyncThing(args) { result in
		// Back on the main thread.
		guard !isCanceled else {
			return
		}
		doSomething(result)
		informDelegateOfCompletion(self)
	}
}

To sum up: because we have GCD, we don’t need an operation queue that handles concurrency. In fact, we don’t even want it to handle concurrency.

MainThreadOperationQueue, MainThreadOperation

MainThreadOperationQueue and MainThreadOperation are part of our RSCore framework.

A few things to note:

  • MainThreadOperation is a Swift protocol. You don’t subclass it; you implement it.
  • When an operation is finished, it informs the queue by calling a method on the main thread. No KVO or other form of notifications.
  • The code is quite simple, but there’s more of it than you might expect: managing dependencies adds to the bulk. The code is basically just a thing that does a bunch of housekeeping.
  • It uses precondition(Thread.isMainThread) in a number of places, which will crash even a Release-build app. We use precondition over assert quite deliberately: if these functions are called outside the main thread, then we’re operating in a surrealistic environment, and we refuse to go on.
  • It has some tests, but of course it should have more tests.

At this writing, this code is already running on over 2,300 iOS devices, since it was included in NetNewsWire 5.0 TestFlight build 28 last night. That was the build where we switched our Feedly syncing code from OperationQueue to our own MainThreadOperationQueue. (I just checked number of installations in App Store Connect.) So far so good!

One bonus thing I like about it is that any given MainThreadOperation can create its own MainThreadOperationQueue to run a bunch of child operations, and then call back only when those operations have finished or are canceled. (We use this feature as part of Feedly syncing.)

But my favorite thing about it is how low-tech it is. There’s not even a hint of cleverness in that code — it’ll put you to sleep. But this makes bugs, including crashing bugs, way less likely.

Don’t Do This (Usually)

Normally I am very much against reinventing wheels. No points are awarded for writing your own version of a thing that 1) comes with the system and 2) is well-suited to your needs — instead, points are taken away for showing off and wasting time.

Even if a system-provided thing is not exactly right, but close enough, you should probably use it.

But when you do find you need write your own version of the wheel, think about what you need exactly, and just do that thing and not more. And don’t be clever — you don’t want to trade one problem for another.

Update 1:20 pm: I’ve been asked about using OperationQeue.main to run code on the main thread and still use OperationQueue. Answer: the thing that triggered all this was the mystery KVO crashes. I don’t want to dwell on that, but that’s really why we wrote our own operation queue.

Immutable Swift Structs

As you read this, consider that I might be a squirrel and not, as I like to pretend, a rational programmer.

My Two Brains

My Objective-C brain — which will never leave me, even though I’ve been writing in Swift for years now — always told me to create immutable objects as often as possible.

Objective-C brain says, “Immutable means you never have to worry about it.”

My Swift brain — which is newer, and has all the fun these days — tells me another thing.

Swift brain says, “Value types are good. Use value types as often as possible.”

So my favorite thing in all of Swift is an immutable struct. It’s a value type, and it can’t be mutated!

Both brains happeeee! 🐥🐣

Var vs. Let

This came up in working on NetNewsWire: some model objects — some structs decoded from JSON returned by a syncing server — used var instead of let for their properties.

These model objects are not mutated anywhere. So, I figured, those declarations should use let instead. (Which would make my brains happy!)

An immutable struct is, after all, the greatest thing ever, right? You don’t have to worry about it. It never gets copied (at least in theory). If you share it across threads, it’s the same everywhere: you don’t get some number of different copies.

So I asked the author to change the vars to lets.

I didn’t expect the response — which was “Why?” (Asked earnestly, of course.)

I struggled with the answer.

Value Types Are Good

Here’s the thing: value types are good. Even though those structs aren’t being mutated now, we could imagine cases where they could be.

Why not leave it up to the code that has one of these to decide to mutate or not? After all, as value types, this makes a copy and doesn’t affect other instances. Value types are safe, regardless of mutability.

And yet…

Here’s Where I Might Be a Squirrel

I still insisted on using let for the properties in these structs.

There’s something to be said, of course, for always starting out strict and loosening up only when it’s actually needed. Don’t loosen up in anticipation. But that’s hardly a big deal here.

Instead I think it goes to code-reading: if I see let instead of var, I have some understanding of the intent. In this case, the structs are parsed versions of stuff-from-the-server, and that’s a case where you could expect immutability, and be surprised to find something else.

Those structs don’t get saved anywhere as-is; they don’t live long; nothing the user does can change them.

These structs are a report: “Server says this.”

So… I think it’s the right call, but I’m absolutely aware of the possibility that I’m just distracted by my Objective-C instincts, and I’m over-tidying.

I don’t know! What do you think? Should I have left it as-is?

The NetNewsWire team continues to rock — there’s a new TestFlight build up.

Note the change notes — they represent just three days of work. We’ve got the best team. 🎸

NetNewsWire for Mac Mystery Scrollwheel Crash

For NetNewsWire for Mac, I get one or two crash logs a week referencing scrollView:​scrollWheelWithEvent:.

Here’s the bug for it.

Chris Campbell, in a comment, writes:

FYI, I see this crash reported very frequently in the commercial Mac app I work on at my Day Job. Unfortunately, we’ve never been able to reproduce it first-hand. I used a Tech Support Incident to correspond with Apple about this issue (giving them hundreds of sample crash reports), only to have them ultimately give up and credit back the Incident.

-scrollView:scrollWheelWithEvent: is an API of the private NSScrollingBehavior class and its subclasses (class cluster). Those objects are an implementation detail of NSScrollView that are stored in an external structure, so there is significant opportunity for mismanagement of the memory references.

There has been speculation that “responsive scrolling” is somehow involved…

If you know more about this bug, or have more ideas for working around it, please comment on the issue. Thanks!

Update 2:20 pm Jeff Nadeau writes:

This should be fixed in 10.15. The 10.15.2 crash I see linked is something different.

This is great news! I hadn’t noticed that almost all of the scrollWheel-related crashes were in 10.14.

I have just one crash log that references the scrollWheel on 10.15, and it includes this:

Assertion failed: (![currentGestureList containsObject:​self]), function NSScrollingBehavior​ConcurrentVBL​AddToCurrent​GestureList, file /BuildRoot/​Library/Caches/​com.apple.xbs/Sources/AppKit/​AppKit-1894.20.140/Scrolling.subproj/​NSScrolling​BehaviorConcurrentVBL.m, line 102.

Estimating NetNewsWire for iOS Demand

As we get closer and closer to shipping NetNewsWire for iOS, I’m starting to realize that the iOS version could be quite a bit more popular than the Mac app.

Here are a few numbers:

  • NetNewsWire 5.0 for Mac was downloaded 24,789 times.
  • The NetNewsWire for Mac beta builds are downloaded anywhere from around 630 to around 1,360 times.
  • Number of people who signed up for the public test of NetNewsWire for iOS: 5,263.

Five times more?

The number of testers tells me — very roughly — that there’s about five times more interest in the iOS app than in the Mac app.

If that holds true, then NetNewsWire 5 for iOS, once it ships, will get downloaded about 124,000 times.

Let’s round down — call it 100,000 downloads in the first two weeks. I would be amazed if we got that that many downloads. I’m really expecting a number much closer to the Mac app. But I could be very wrong — I admit to thinking like a Mac developer and not really feeling the greater size of the iOS market.

It’s quite possible that 100,000 might be too low.

At any rate, I’ll be sure to report the actual numbers on this blog. 🐣

What does this mean for interest in RSS readers in general?

Remember that NetNewsWire is just one of many RSS readers on Mac and iOS. There are readers for Android and Windows — and a whole bunch of browser-based readers, such as Feedbin, Feedly, Inoreader, and others.

You can’t just multiply my NetNewsWire estimate by the number of apps and come up with something meaningful.

But you can look at some things and get an idea. For instance: on Feedly, one of the most popular browser-based readers, there are 1.6 million people subscribed to Engadget. No, I have no idea if those are all active readers, but still.

I think it would be reasonable to guess that the number of people who use an RSS reader is probably greater than a million, and could be several million people.

Those aren’t Twitter user numbers, obviously, not even close — but I’m betting it’s more than what most people assume it is.

PS This blog has 16K followers just on Feedly. If it were possible to add up my subscriber counts from each RSS reader, it would be quite a bit more than the 18K Twitter followers I have. Which is as it should be.

The New Ranchero Software

Ranchero Software, my old company, has actually technically existed in three different forms over the years — I’ve been an indie, then got a job and shut down the company, went back to indie and rebooted it, got a job, etc.

The most recent version — Ranchero Software, LLC — shut down a while ago. A year ago? I forget.

We got tired of reporting every month that it made $0. And we realized we had no plans to ever make money, since, well, I have a great day job at Omni.

But now it’s back, in a new way

The new Ranchero Software isn’t a company: it’s an organization on GitHub. Just today we moved NetNewsWire and associated repos to this new location.

We realized it would be easier if we had an actual organization with various teams — easier to manage, easier to add people, easier to set permissions, etc.

There was a little talk about what to call it. Name it after NetNewsWire? Or use the name Ranchero?

One of the things to remember is that it’s not just about NetNewsWire: there are also stand-alone frameworks and there’s another app: Rainier. (Which is way behind NetNewsWire. Nothing to see yet.) And we might do other apps, too.

So while we could have — a la the Apache folks — named the org after its founding project NetNewsWire, everyone preferred using the Ranchero name.

For me, at least, this is super cool. Ranchero Software lives!

PS You might wonder where the name Ranchero came from. It was 1996, and we were looking for a domain name that was 1) available, and 2) trendy. In those days, the hot stuff was Tango and Marimba and similar. We found that ranchero.com was available, and it kinda fit in with those names — plus it sounded kinda western — so we grabbed it.

PPS No, Ranchero Software isn’t an actual organization with papers filed anywhere. It’s just a thing on GitHub. But that’s all it needs to be.

My New Year’s Resolution Is to Focus My Anger

For the last few years I’ve found myself getting angry at small things, which is so unlike me.

My resolution is to try harder to get angry only when it’s actually worth it. I can be angry at cruelty, angry at the forces destroying democracy for their own corrupt power, angry at the malevolences driving our climate crisis.

But I need to not get angry just because Instruments won’t profile my app, or I get a robocall, or someone on Twitter completely misses the point of something I wrote.

Anger — righteous, smashing anger — is completely called-for right now. But only at the right targets. This is the hard part, at least for me — it starts to spill out to where it doesn’t belong, and I can feel it corroding me.

I’m not by nature an angry person. Quite the opposite! But I do care deeply about some things.

My emotions somehow need to get smarter about what’s worth the anger and what things are not actually part of the current vast project of destruction.

I don’t know how to do that other than to constantly remind myself, when I feel it bubbling up over something small, that it’s not worth it.

But the problem with anger — even our current justifiable and necessary anger — is that it becomes a habit, and then it goes blind.