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:
- Try to get a Mac Catalyst version working
- Re-write it (not really an option at this stage, so close to the competition)
- 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 clone
ed the project and hit “Build and Run”.
It built and ran!