Resolving Modern Mac Alias Files

When a user selects a file in the Mac Finder and chooses File -> Make Alias, the resulting “copy” is a kind of smart reference to the original. It is similar to a POSIX symbolic link, but whereas a symbolic link references the original by full path, an alias has historically stored additional information about the original so that it stands a chance of being resolved even if the original full path no longer exists.

An alias, for example, can usually withstand being copied from one volume to another. To test this: create a Folder in your home folder called “Test”, and a file within it called “File”. Now, make an alias to “File”. You should have a folder hierarchy that looks like this:

Test
	File
	File alias

Click on the “File Alias” item, then choose “File” -> “Show Original” from the Mac menu bar to see how it resolves to the original.

Now, copy the whole folder to another volume, for example to a thumb drive or other external drive on your Mac. Click on the alias file and “Show Original” again. Instead of resolving to the file on your home volume, it resolves relative to the location on the new volume.

That’s pretty neat.

In the old days, if a developer needed to resolve an alias file on disk, they would use a Carbon function such as FSResolveAliasFile. In recent years, particularly as Application Sandboxing was introduced on the Mac, Apple has shifted away from aliases as a developer-facing concept, towards the use of “Bookmark Data”. As a result, an “alias file” created in the Finder is no longer a traditional alias, but a blob of bookmark data. You can confirm this by examining the hex content of the “File Alias” you created above. From the Terminal:

% xxd File\ alias
00000000: 626f 6f6b 0000 0000 6d61 726b 0000 0000  book....mark....
00000010: 3800 0000 3800 0000 f003 0000 0000 0410  8...8...........
00000020: 0000 0000 0061 0000 768c 979b 7ed8 be41  .....a..v...~..A
00000030: 0000 0000 ff7f 0000 0403 0000 0400 0000  ................
00000040: 0303 0000 0004 0000 0700 0000 0101 0000  ................

It was nice of them to design the format with the tell-tale “book….mark” data right there in the header!

Although you can still use FSResolveAliasFile on these beasts, the function is deprecated and Xcode will warn you about such behavior. The way forward is to use the newer

-[NSURL URLByResolvingBookmarkData:...]

method in Objective-C, or

URL.init(resolvingBookmarkData: ...)

in Swift. Just load the NSData from the alias file’s URL, and pass it to NSURL/URL as appropriate.

There’s a big catch, however, which is that you must take care to pass the alias file’s URL as the “relativeTo:” parameter when resolving the bookmark. Otherwise the bookmark will resolve as expected in typical scenarios, but will fail to resolve in all the scenarios where bookmarks really shine, as for example in the case of moving a bookmark and its target to another volume. So, for example, to resolve an alias file you know exists at “/private/tmp/Test/SomeAlias”:

var targetURL: URL? = nil
do {
	let aliasFileURL = URL(fileURLWithPath: "/private/tmp/Test/SomeAlias")
	let thisBookmarkData = try URL.bookmarkData(withContentsOf: aliasFileURL)
	var ignoredStaleness: Bool = false
	targetURL = try URL(resolvingBookmarkData: thisBookmarkData, options: .withoutUI, relativeTo: aliasFileURL, bookmarkDataIsStale: &ignoredStaleness)
}
catch {
	print("Got error: \(error)")
}

Notice how I ignore the staleness? It’s because I think this notion of staleness is tied more to resolving data with security scope, or in any case a bookmark that you are holding as pure Data, and not one that persists as a file on disk. The safety of ignoring staleness is supported by the fact that, starting in macOS 10.10, there is a new convenience method on NSURL specifically for resolving “alias files”:

var targetURL2: URL? = nil
do {
	let aliasFileURL2 = URL(fileURLWithPath: "/private/tmp/Test/SomeAlias")
	targetURL2 = try URL(resolvingAliasFileAt: aliasFileURL2)
}
catch {
	print("Got error: \(error)")
}

Which takes care of ensuring the “relativeTo:” information is considered, on your behalf. If you don’t need to support Mac OS X 10.9 or earlier, you should use the newest method! Otherwise, I hope the information preceding is of some use.