NSURL is one of the main and most used classes of Cocoa, and rightly so. It is a very useful class that allows to handle local and remote resource locations seamlessly. But when it comes to comparison and equivalency between two files (represented by their NSURL instances), NSURL doesn’t offer a reliable method for checking whether two files represent the same resource on disk.
NSURL, being a foundation class inheriting from NSObject, has its
isEqual: method. However, according to the documentation, in NSURL, the
isEqual: method is defined as:
Two NSURLs are considered equal if and only if they return identical values for both
This means that, unless the two NSURLs are defined in exactly the same way, they will be different according to the result of
isEqual:. Now, when managing files, chances are that you will find yourself in need of checking the equality of two files that have been defined differently (i.e: one extracted from NSFileManager and another one directly created by a path in a NSString). Thus, you can have two resources looking like:
- NSURL * path1, represented as file://Users/user/Library/Application Support/MobileSafari/a.txt
- NSURL * path2, represented as ~/Library/Application%20Support/./MobileSafari/a.txt
We know that both represent the exact same file on disk, but NSURL
isEqual: will be unaware of this. Unfortunately, there is no reliable way of comparing two files represented as NSURL, instead, you need to compare their paths as NSString.
The first step to compare them properly is to get their paths using the method
path from NSURL, and then standardizing the results by means of the NSString method
stringByStandardizingPath. In the case depicted above, this will transform both file paths as:
So now we can compare both strings. But there is still another situation we must take into account, links and symbolic links. In the case of a symbolic link, string equality between path1 and path2 will not tell us if path2 is a symbolic link to the other one. Contrary to what the documentation of
This method can make the following changes in the provided string: […] In absolute paths only, resolve references to the parent directory (that is, the component “..”) to the real parent directory if possible using stringByResolvingSymlinksInPath, which consults the file system to resolve each potential symbolic link.
the method does not actually deal nicely with references to current/parent directory. Thus, we need to resolve the paths to get to the real path of the file. While we could stick to the C API for this, using realpath, there is a simpler way in Cocoa by means of the NSString method
stringByResolvingSymlinksInPath:, that like their realpath C counterpart, needs both files to exist in order to successfully resolve both paths and decide whether or not they point to the same resource on disk, but if one of the paths doesn’t exist on disk, then obviously they are not pointing to the same resource, right?
So all we need to do is, in case the previous checks didn’t found both URLs to be the same, invoke
stringByResolvingSymlinksInPath: on both paths and then compare if they are the same by calling NSString’s
I have created a category to check equality between file NSURLs. I will expand it soon to include equality between remote internet resource URLs. You can find it on github.
- NSURL can’t be used on its own to compare the equality or equivalency of two resources with aparently different routes, but pointing to the same resource. You must use NSString and compare both paths.
- Avoid [NSURL fileURLWithPath:…] like the plague. Not only it misinterpretes universal simbols (like home directory ~, which for fileURLWithPath: means the current working directory) but it also sets a weird schema for comaring with other NSURL files. My advice is to use [NSURL URLWithString:…].
- stringByStandardizingPath does a good job to standarize the path of a NSURL, but is not that good resolving symbolic links and other relative references.