KISS

Keep It Simple Stupid

Fast swift rebuilds with fswatch

| comments

I normally use AppCode rather than Xcode for iOS development because it’s so much more configurable and keyboard-friendly, which is even better with its ideavim plugin. Its biggest downside for me is that it eats a lot of CPU, especially while indexing when a project has been opened. High CPU usage means fast battery drain (and higher laptop temperature, and audible fans). One time I needed to “survive” on the MBP’s battery for several hours, so I couldn’t use AppCode.

Coincidentally, I had a task of importing data from some tables in a PDF file into swift models. That means pasting the table data in whatever format it got to be copied into the swift file, cleaning it up, adding regular structure to it (inserting commas, wrapping lines in function calls). An excellent usage of vim because of macros, registers, regexps. I wanted to have regular feedback though because the model types kept becoming more specific as I was adding more data. The solution was to use fswatch to automatically rebuild the project.

The project had multiple targets for different functionality, so I was able to build only the target where I was updating the file. It was significantly faster and more energy-efficient than building the main application target.

The setup was two vertical panes in tmux, one with vim and one with fswatch (brew install fswatch). This let me quickly see the automatic build results every time I saved the file. The command was:

1
$ fswatch -0 -o -l0.1 ExternalAPI/Model.swift | xargs -0 -n1 -I{} sh -c 'echo "\n\n\n\n\n\n\n\n\n\n\n\n"; noti xcodebuild -quiet -workspace MyApp.xcworkspace -scheme ExternalAPI -configuration Debug -sdk iphonesimulator -arch x86_64 build | head -20'

The core idea of using fswatch to monitor a file and execute a static command is from this StackOverflow answer: How to run fswatch to call a program with static arguments?. The command watches the file ExternalAPI/Model.swift in the ExternalAPI target and when it changes, launches a build of the target (-scheme ExternalAPI).

A couple details:

  • The | head -20 is to limit the number of errors printed to the console because I’m interested in them from the top and if there is a lot of the errors, I’d need to switch to the second pane and manually scroll the output, which would slow me down.
  • noti is to get notified with an OS X notification when a build finishes; then if you don’t see any errors in the output, it means the build has succeeded. I tried making this more explicit by printing “Success”, but couldn’t figure it out in a few tries; it’s not a big deal though. …
  • …Instead I inserted the echo with multiple newlines so that the previous output automatically scrolls up when a new build is started — this helped with the case when I had multiple successful builds in a row and they printed nothing, so I didn’t really know if a new build has started or finished yet.

This setup worked very well for me and I was able to import all the data in half a day, with more than 50% battery available by then.

ps. An alternative could be to clear screen before every build (replacing echo with clear):

1
$ fswatch -0 -o -l0.1 ExternalAPI/Model.swift | xargs -0 -n1 -I{} sh -c 'clear; noti xcodebuild -quiet -workspace MyApp.xcworkspace -scheme ExternalAPI -configuration Debug -sdk iphonesimulator -arch x86_64 build | head -20'

Comments