Working with App Sandboxing

| Aug 4, 2013 min read

Writing process automation software on the Mac has been something I’ve always been interested in. Basically you set up a working folder somewhere that you drop files in to and then your application does a bunch of crazy stuff to the file along a specific workflow chain.

Recently I had the opportunity to write some software that does this kind of thing and I wanted to submit it to the app store for sale (more on this later.)

When you first access a file using the NSOpenPanel you are granted rights to use the resource you select whether it’s a folder or a file. Ordinarily we would store this location in our user defaults (preferences) for later reuse; with sandboxing however this becomes a bit trickier. It’s actually pretty simple once you understand the process that Apple wants you to follow.

Instead of storing the string value of the path to the resource on disk, you capture the path as an NSURL. From this url you make a “Bookmark.” Actually, this bookmark is encoded NSData. You can think of the contents of the bookmark as a record of the event of the user selecting the resource using the open panel.

You then store the bookmark in your preferences as normal; along with the path if you’re feeling spry. When you need to access the resource again later you simply resolve the bookmark and start using it.

In your NSOpen handler

NSData *resourceData = [openPanel.URL
                bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
         includingResourceValuesForKeys:nil
                          relativeToURL:nil
                                  error:&err];

[defaults setValue:resourceData forKey:@"resourceBookmark"];

// Check for error and provide the user with some feedback if things go badly.

Later when you need the resource again:

// ## Hydrate Bookmark
// Return an URL that encapsulates our working resource.
- (NSURL *) hydrateBookmark
{
    if ([defaults valueForKey:@"resourceBookmark"] == nil) {
        NSLog(@"Don't even think about it.");
        return nil;
    }

    BOOL stale;
    NSError *bookmarkErr;

// The crucial element here is NSURLBookmarkResolutionWithSecurityScope
// Which allows us to take advantage of the com.apple.security.files.bookmarks.app-scope
// entitlement - Don't forget to add that key to your entitlements plist!

    NSURL *resourceUrl = [NSURL URLByResolvingBookmarkData:[defaults valueForKey:@"resourceBookmark"]
                                                   options:NSURLBookmarkResolutionWithSecurityScope
                                             relativeToURL:nil
                                       bookmarkDataIsStale:&stale
                                                     error:&bookmarkErr];

    BOOL accessAllowed = [resourceUrl startAccessingSecurityScopedResource];

    if (bookmarkErr || stale) {
        NSLog(@"Bookmark cannot be accessed or is stale.");
        return nil;
    }

    return resourceUrl;
}