Under The Microscope

Avoiding Crashes Caused by Application Moves

One of the best RSS readers on the Mac, NetNewsWire, has returned home to its original developer Brent Simmons. Late last month, Brent and his team of volunteers shipped version 5, and I’m delighted to see the return of NetNewsWire on the Mac.

Along with the release, Brent has been posting frequently to his blog at inessential.com. While discussing a piece he’d published on post-release follow-through, Brent noted “I keep remembering that I know things that I figure everyone knows — but then I remember that they don’t. So I write ’em up”.

This is a noble practice, and it’s inspired our own post, where we’ll be sharing some information and code.

Avoiding Crashes Due to Moved Applications

Following the release of NetNewsWire 5.0.0, Brent discovered the application had a crashing bug. Working with Daniel Jalkut of Red Sweater, they determined that the problem occurred when the application was moved in the Finder. When I saw Brent’s post detailing this issue, I knew we could help.

For many years, Rogue Amoeba’s applications have guarded against this very issue. If one of our applications is moved while it’s running, it will display an error like this:

This alert lets the user know they’re likely to run into issues, and urges them to quit and relaunch.1

Sample Code and Details

I discussed this issue with Daniel and Brent, and provided them with the code we’d been using to watch for this issue. This actually led us to make several changes and tweaks, and a simplified implementation of this “Application Moved” watcher can be found below:

- (void)_showBundleMovedAlertIfNeeded
{
    //This depends on bundleURL being cached and not updated when the app moves
    //Perhaps someday that will be false, but it's true now - 2019-09


    if( [[[NSBundle mainBundle] bundleURL] checkResourceIsReachableAndReturnError: nil] )
        return;

    NSString *appname = [[NSProcessInfo processInfo] processName];
    NSString *messageFmt = @"%@ has been moved or renamed";
    NSString *infoFmt = @"To prevent errors, %@ must be relaunched.\n\nIf you cannot quit immediately, click Continue, then quit and relaunch as soon as possible to avoid problems.";

    NSAlert *alert = [[NSAlert alloc] init];
    [alert setAlertStyle:NSAlertStyleCritical];
    [alert setMessageText:[NSString stringWithFormat:messageFmt, appname]];
    [alert setInformativeText:[NSString stringWithFormat:infoFmt, appname]];
    [alert addButtonWithTitle:@"Quit"];
    [alert addButtonWithTitle:@"Continue (Not Recommended)"];
    
    NSInteger alertButton = [alert runModal];
    [alert release];
    
    if (alertButton == NSAlertFirstButtonReturn)
        [NSApp terminate: self];
}

- (void)applicationDidBecomeActive:(NSNotification *)notification
{
    [self _showBundleMovedAlertIfNeeded];
}

This code was inspired by changes from Daniel, as well as Rich Siegel of Bare Bones. Whenever the application becomes active, we look if the bundle still exists where it should be, and throw an alert if not. This implementation is by no means perfect. For instance, if the application is moved and not activated for awhile, it could fail to notify the user before a problem hits. On the other hand, it gracefully handles other difficult edge cases, like the parent folder of the application being moved.

This is part of the shared code we use in all our applications, so improving it in one place will benefit all of our products. It’s good to revisit and refine things over time, and sharing this code with others has provided us with a nice opportunity to do so.

Closing

If you’re a Mac developer, you might have just realized why some previously inexplicable crashes have been occurring. With the above, you’re well on your way to handling the issue and avoiding crashes.

Over nearly 17 years of developing software for the Mac, we’ve created a lot of useful internal systems and generated a great deal of institutional knowledge. It’s good to be reminded that this accumulated knowledge can help other developers too. We’ll be keeping an eye out for more things we might share with other Mac developers.


Footnotes:

  1. It’s likely that most applications should simply force the user to quit, but as you can see, we also provide a “Continue” option. For our applications, the user may be in the middle of an important and uninterruptible task, like making a recording, so we don’t want to force them to quit the application. ↩︎

Our Software