A tale of performance issues, new shiny things, old hardware and patching Big Sur

I sat down many moons ago and read through the challenge page for Up the Garden Path. Voice control is what screamed out at me (450 bonus points! 🙌). Thus, after some investigation of on-board recognition, I decided to offload both the recognition and the audio capture to my ancient 15-inch Macintosh Book Professional and write my first Macintosh application. So, armed with Xcode 11 and an installation of MacOS Mojave, I sat down and started exploring the NSSpeechRecognizer API.

The documentation for this API was alright. I found it difficult to understand, but that was almost certainly due to my lack of experience with AppKit. The third-party resources for this element of macOS are almost nonexistent, however. I ended up following a video tutorial that got me off the ground.

And then I stopped and thought. I don’t have a wireless microphone. That means that I’d have to lug the entire computer outside to the arena. Admittedly, its weight is insubstantial (that’s the whole point of a laptop), but wouldn’t it be cooler if I could develop an iOS app instead?

And that is how I came to learn about Apple’s newer, cross-platform Speech framework. There was a downside, though: I wouldn’t be able to specify a custom grammar. It’s my only option on iOS, though.

So I started searching for React Native libraries that interfaced with this API, and I found one pretty quick: react-native-voice.1 I fired up iTerm2 and ran brew install node watchman and sudo gem install cocoapods. I had to do some finicking around to get the latter to work, but in the end it wasn’t too bad.

And then I ran drew a breath and ran npx react-native init DashTen ---template react-native-template-typescript. That long-winded command set up a TypeScript-based project called DashTen (as in: “Speed -10”). I promptly removed all the npm-specific files, thus replacing it with my preferred Node package manager, pnpm.

And there I encountered my first hiccup: the development server used by React Native for things like hot-reloading, metro, doesn’t follow symlinks properly, which are crucial to pnpm’s design.

And so I reluctantly switched back to npm.

And so, drawing my breath, I ran npx react-native start.

Nothing happened for a while. ⏳ And then… it spat out a complicated error and exited.

🤦‍♂️ sigh

I did some DuckDuckGo-ing and found that this error was due to having an older version of XCode. I was using the latest version available on Mojave, however — which means I’ll have to upgrade to Catalina.

After finishing the upgrade to Catalina, I ran npx react-native start again. And you know what happened?

Not much for a while. A top-o'-the-line Mid-2012 MacBook Pro 15" struggles to do any mildly intensive work on Catalina.

And then the iOS Simulator opened.

It was like magic — I had gone from no signs of life to the booting of a simulated operating system.

The iOS Simulator takes a little while to boot, so I went and did some other stuff while I waited.

When I came back after ten minutes or so, it had booted!

And it was slow. The Simulator had gobbled up most of the 8GB of RAM I had available. Oh well. I guess I’ll just have to debug on-device. Now that I think about it, I couldn’t have really used the Simulator anyway — on-device speech recognition is best on an actual device. So I tried the same command, specifying the flags to tell it to run on-device. And… nothing happened for a long time, so I terminated the process.

And I tried to build it directly from Xcode.

It took a long time, so I terminated it before it finished.

Turns out React Native doesn’t run well on a machine from eight years ago.

And then I had my stroke of genius. ⚡ What if I reduced the complexity of all of this by simply writing a UIKit application? I had done some iOS work before in Swift, and it was alright — good, even. So I clicked File → New Project in Xcode, chose the most basic UIKit template possible, and created the project.

My next challenge was actually writing the code. I knew that traditional UIKit apps were structured around an MVC architecture, so I went with that. I opened up a storyboard2 and got to work putting a single button in the middle of the screen. I subsequently hooked it up to the relevant code in my view controller, added the relevant event handler, and I was off to the races. The code to start and stop speech recognition is somewhat convoluted, but it was easy enough once I got the hang of it. I hacked together some regex-based parsing code, a simple web server to run on the Pi, and I was done!

Mostly.

And then, a few months later, I upgraded to iOS 14.5…

Every year, Apple release a version of Xcode that is incompatible with the previous version of macOS. Normally, that’s not a problem. You just upgrade. But macOS Big Sur, the OS required, does not run on a Mid-2012 MacBook Pro.

I had three options:

  1. Try to get a Mac Catalyst version working
  2. Re-write it (not really an option at this stage, so close to the competition)
  3. Try to get Big Sur running on it

I went for the third.

I did some DuckDuckGo-ing, and I found a tool called Patched Sur, put together by a person called Ben Sova. It’s an open-source project which patches the Big Sur installer provided by Apple to fool it into running on older Macs.

It’s fantastic. ✨ I encountered some minor issues, but for the most part it worked like a charm. I downloaded and launched Xcode on my suddenly new-looking computer, git cloneed the project and hit “Build and Run”.

It built and ran!


  1. Yes, I know they have problems. But I’ve used them before, and I figured it was the quickest way to get started. ↩︎

  2. If there’s one thing that can be said for the React ecosystem, it’s that you can find a library for pretty much everything. ↩︎