App Movement Monitoring

September 5th, 2019

I’ve been helping out when I can with the NetNewsWire open source project. Recently, Brent shared one of only a couple crash reports he’s received, in which the only plausible explanation would be if the app’s own internal files had failed to load. I recognized the likely cause of the problem immediately, because …

The Problem

Essentially, when a Mac app is launched, the location of that app on disk is saved, and used repeatedly whenever an internal component needs to located. The dynamic nature of resource loading in Mac apps means that these components are not typically loaded until they are need. For example, if you never show the Preferences window in a typical Mac app, the resources that define that window will never be loaded.

If, on the other hand, you decide to show the Preferences window, but you’ve moved the app since it was launched, things have a tendency to go haywire. The app will go searching for its Preferences resources in the location on disk where they used to be, and it won’t find anything. In the best case, the Preferences window simply fails to show. In the worst case? Assumptions about the infallibility of loading resources are proven wrong, and the app may crash.

A Solution

For many years, I’ve had an open ticket in my bug tracking system to address this same problem in my own apps. It’s not a completely critical problem, in part because it affects only a small subset of users, and only at the critical time when they have just moved or renamed an app. But the larger one’s user base is, the more examples of these hard-to-reason crash reports there will be. For a developer who is diligent about addressing defects in application code, these crash reports are both a distraction and a real concern.

Talking with my friend Paul Kafasis of Rogue Amoeba, he confirmed that they had also seen such issues over the years, and had already implemented a solution. He shared their pertinent source code with me, and has since shared it publicly on their company blog.

The Rogue Amoeba solution, in a nutshell:

  1. Watch the location of the app on disk.
  2. Prompt the user if it moves, offering a choice to quit or proceed with caution.
  3. Quit the app after confirmation by the user.

Given a head start by their code, I decided to finally tackle this problem in my own apps, and in NetNewsWire. What the heck? If I’m going to all the trouble, why not develop something so correct, and so dead simple to add, that it would be a no-brainer for other Mac developers to drop in a ready-made solution? It’s available on GitHub:

RSAppMovementMonitor: Gracefully handling the movement of a running app.

As I dug into the problem, I noticed a few areas where I thought I might deviate from Rogue Amoeba’s example. Their solution is tailor-made to the needs of their own apps, but as I thought about the problem in terms of my own apps, and in terms of the typical needs of most apps, I decided to make some changes. Roughly in order of what I perceive highest to lowest priority:

  1. The solution should be implemented in Swift, completely self-contained, and easy to drop in to any Mac app.
  2. The app should offer to relaunch instead of just quitting. This took a little extra work to keep track of both the original and final locations of the app on disk. Luckily my friend Rich Siegel of Bare Bones had good advice for me there.
  3. Relaunching should not be presented as an option, but as a necessity. In some data-critical apps, there may be cause to allow the user to keep the app running. I suspect this is the case for Rogue Amoeba’s apps, which are often doing live audio processing. In most apps however, particularly those without documents or that employ macOS autosave technologies, quitting and restarting the app is a safe operation.
  4. Localized strings should be cached at launch time. Because the prompt to the user will be presented after the app has moved, the localized resources that constitute that panel would not be available by the time they are needed. My solution loads and caches the localized strings so they’re ready if needed.
  5. Monitoring should be more aggressive. Watching for changes on the app bundle itself catches most of the user actions that lead to an app’s location changing on disk, but it misses scenarios such as when the user has renamed a folder containing the app. My solution checks whenever the app becomes active, on the assumption that the user will have made the app inactive before doing something such as renaming a folder in the Finder.
  6. The solution should be extensible through a custom handler, such that apps with differing needs can take advantage of the monitoring functionality, but act differently in response.

I think RSAppMovementMonitor serves the above priorities well, but ideally, none of this would be necessary. If only somebody were in a position to solve it even more elegantly…

Better Solutions

Apple itself could, and probably should, do more to alleviate the impact of this issue. What’s frustrating is they obviously acknowledge the problem, but have thus far done little do help. If you attempt to move a running application to the trash, you are met with a stern dialog forbidding the action. But if you attempt to rename an app, you are met merely with a warning. Finally, if you drag an app from one location to another, the Mac does nothing at all the impede you.

On a high level, there are two tacks Apple could take in addressing this:

  1. Prevent the movement or renaming of running apps.
  2. Mitigate the impact of moving a running app.

The former is probably a more pragmatic solution, along the lines of what I’ve shared here but with more reliable, and unified impact across all running apps on the system. The latter would require some kind of systematic change in the frameworks with respect to the way application resources are located and loaded at runtime. I can imagine a lot of solutions here but they may frankly be too much to bother with trying to patch on to the existing system.

MacOS 10.15 is, by all accounts, nearing completion, and there is no sign of a change in the status quo. So I suppose we can look forward to 10.16 and hope for some improvement at the system level. In the mean time, I hope that RSAppMovementMonitor proves useful either as a drop-in solution, or as an inspiration for more specialized handling of this situation in other Mac apps.